update prometheus dependencies (#5520)

Signed-off-by: junot <junotxiang@kubesphere.io>
This commit is contained in:
junot
2023-02-14 09:46:22 +08:00
committed by GitHub
parent a979342f56
commit 2cd5f45d47
769 changed files with 81283 additions and 30511 deletions

View File

@@ -23,12 +23,11 @@ import (
"fmt"
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// New creates a new alert API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
@@ -40,16 +39,27 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
GetAlerts(params *GetAlertsParams, opts ...ClientOption) (*GetAlertsOK, error)
PostAlerts(params *PostAlertsParams, opts ...ClientOption) (*PostAlertsOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
GetAlerts Get a list of alerts
*/
func (a *Client) GetAlerts(params *GetAlertsParams) (*GetAlertsOK, error) {
func (a *Client) GetAlerts(params *GetAlertsParams, opts ...ClientOption) (*GetAlertsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetAlertsParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "getAlerts",
Method: "GET",
PathPattern: "/alerts",
@@ -60,7 +70,12 @@ func (a *Client) GetAlerts(params *GetAlertsParams) (*GetAlertsOK, error) {
Reader: &GetAlertsReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
@@ -77,13 +92,12 @@ func (a *Client) GetAlerts(params *GetAlertsParams) (*GetAlertsOK, error) {
/*
PostAlerts Create new Alerts
*/
func (a *Client) PostAlerts(params *PostAlertsParams) (*PostAlertsOK, error) {
func (a *Client) PostAlerts(params *PostAlertsParams, opts ...ClientOption) (*PostAlertsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewPostAlertsParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "postAlerts",
Method: "POST",
PathPattern: "/alerts",
@@ -94,7 +108,12 @@ func (a *Client) PostAlerts(params *PostAlertsParams) (*PostAlertsOK, error) {
Reader: &PostAlertsReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}

View File

@@ -27,119 +27,96 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
strfmt "github.com/go-openapi/strfmt"
)
// NewGetAlertsParams creates a new GetAlertsParams object
// with the default values initialized.
// NewGetAlertsParams creates a new GetAlertsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetAlertsParams() *GetAlertsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
unprocessedDefault = bool(true)
)
return &GetAlertsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
Unprocessed: &unprocessedDefault,
timeout: cr.DefaultTimeout,
}
}
// NewGetAlertsParamsWithTimeout creates a new GetAlertsParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewGetAlertsParamsWithTimeout(timeout time.Duration) *GetAlertsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
unprocessedDefault = bool(true)
)
return &GetAlertsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
Unprocessed: &unprocessedDefault,
timeout: timeout,
}
}
// NewGetAlertsParamsWithContext creates a new GetAlertsParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewGetAlertsParamsWithContext(ctx context.Context) *GetAlertsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
unprocessedDefault = bool(true)
)
return &GetAlertsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
Unprocessed: &unprocessedDefault,
Context: ctx,
}
}
// NewGetAlertsParamsWithHTTPClient creates a new GetAlertsParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewGetAlertsParamsWithHTTPClient(client *http.Client) *GetAlertsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
unprocessedDefault = bool(true)
)
return &GetAlertsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
Unprocessed: &unprocessedDefault,
HTTPClient: client,
HTTPClient: client,
}
}
/*GetAlertsParams contains all the parameters to send to the API endpoint
for the get alerts operation typically these are written to a http.Request
/*
GetAlertsParams contains all the parameters to send to the API endpoint
for the get alerts operation.
Typically these are written to a http.Request.
*/
type GetAlertsParams struct {
/*Active
Show active alerts
/* Active.
Show active alerts
Default: true
*/
Active *bool
/*Filter
A list of matchers to filter alerts by
/* Filter.
A list of matchers to filter alerts by
*/
Filter []string
/*Inhibited
Show inhibited alerts
/* Inhibited.
Show inhibited alerts
Default: true
*/
Inhibited *bool
/*Receiver
A regex matching receivers to filter alerts by
/* Receiver.
A regex matching receivers to filter alerts by
*/
Receiver *string
/*Silenced
Show silenced alerts
/* Silenced.
Show silenced alerts
Default: true
*/
Silenced *bool
/*Unprocessed
Show unprocessed alerts
/* Unprocessed.
Show unprocessed alerts
Default: true
*/
Unprocessed *bool
@@ -148,6 +125,41 @@ type GetAlertsParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get alerts params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetAlertsParams) WithDefaults() *GetAlertsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get alerts params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetAlertsParams) SetDefaults() {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
unprocessedDefault = bool(true)
)
val := GetAlertsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
Unprocessed: &unprocessedDefault,
}
val.timeout = o.timeout
val.Context = o.Context
val.HTTPClient = o.HTTPClient
*o = val
}
// WithTimeout adds the timeout to the get alerts params
func (o *GetAlertsParams) WithTimeout(timeout time.Duration) *GetAlertsParams {
o.SetTimeout(timeout)
@@ -259,88 +271,96 @@ func (o *GetAlertsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Reg
// query param active
var qrActive bool
if o.Active != nil {
qrActive = *o.Active
}
qActive := swag.FormatBool(qrActive)
if qActive != "" {
if err := r.SetQueryParam("active", qActive); err != nil {
return err
}
}
}
valuesFilter := o.Filter
if o.Filter != nil {
joinedFilter := swag.JoinByFormat(valuesFilter, "multi")
// query array param filter
if err := r.SetQueryParam("filter", joinedFilter...); err != nil {
return err
// binding items for filter
joinedFilter := o.bindParamFilter(reg)
// query array param filter
if err := r.SetQueryParam("filter", joinedFilter...); err != nil {
return err
}
}
if o.Inhibited != nil {
// query param inhibited
var qrInhibited bool
if o.Inhibited != nil {
qrInhibited = *o.Inhibited
}
qInhibited := swag.FormatBool(qrInhibited)
if qInhibited != "" {
if err := r.SetQueryParam("inhibited", qInhibited); err != nil {
return err
}
}
}
if o.Receiver != nil {
// query param receiver
var qrReceiver string
if o.Receiver != nil {
qrReceiver = *o.Receiver
}
qReceiver := qrReceiver
if qReceiver != "" {
if err := r.SetQueryParam("receiver", qReceiver); err != nil {
return err
}
}
}
if o.Silenced != nil {
// query param silenced
var qrSilenced bool
if o.Silenced != nil {
qrSilenced = *o.Silenced
}
qSilenced := swag.FormatBool(qrSilenced)
if qSilenced != "" {
if err := r.SetQueryParam("silenced", qSilenced); err != nil {
return err
}
}
}
if o.Unprocessed != nil {
// query param unprocessed
var qrUnprocessed bool
if o.Unprocessed != nil {
qrUnprocessed = *o.Unprocessed
}
qUnprocessed := swag.FormatBool(qrUnprocessed)
if qUnprocessed != "" {
if err := r.SetQueryParam("unprocessed", qUnprocessed); err != nil {
return err
}
}
}
if len(res) > 0 {
@@ -348,3 +368,20 @@ func (o *GetAlertsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Reg
}
return nil
}
// bindParamGetAlerts binds the parameter filter
func (o *GetAlertsParams) bindParamFilter(formats strfmt.Registry) []string {
filterIR := o.Filter
var filterIC []string
for _, filterIIR := range filterIR { // explode []string
filterIIV := filterIIR // string as string
filterIC = append(filterIC, filterIIV)
}
// items.CollectionFormat: "multi"
filterIS := swag.JoinByFormat(filterIC, "multi")
return filterIS
}

View File

@@ -24,10 +24,9 @@ import (
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// GetAlertsReader is a Reader for the GetAlerts structure.
@@ -56,9 +55,8 @@ func (o *GetAlertsReader) ReadResponse(response runtime.ClientResponse, consumer
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -67,7 +65,8 @@ func NewGetAlertsOK() *GetAlertsOK {
return &GetAlertsOK{}
}
/*GetAlertsOK handles this case with default header values.
/*
GetAlertsOK describes a response with status code 200, with default header values.
Get alerts response
*/
@@ -75,10 +74,39 @@ type GetAlertsOK struct {
Payload models.GettableAlerts
}
// IsSuccess returns true when this get alerts o k response has a 2xx status code
func (o *GetAlertsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get alerts o k response has a 3xx status code
func (o *GetAlertsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get alerts o k response has a 4xx status code
func (o *GetAlertsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get alerts o k response has a 5xx status code
func (o *GetAlertsOK) IsServerError() bool {
return false
}
// IsCode returns true when this get alerts o k response a status code equal to that given
func (o *GetAlertsOK) IsCode(code int) bool {
return code == 200
}
func (o *GetAlertsOK) Error() string {
return fmt.Sprintf("[GET /alerts][%d] getAlertsOK %+v", 200, o.Payload)
}
func (o *GetAlertsOK) String() string {
return fmt.Sprintf("[GET /alerts][%d] getAlertsOK %+v", 200, o.Payload)
}
func (o *GetAlertsOK) GetPayload() models.GettableAlerts {
return o.Payload
}
@@ -98,7 +126,8 @@ func NewGetAlertsBadRequest() *GetAlertsBadRequest {
return &GetAlertsBadRequest{}
}
/*GetAlertsBadRequest handles this case with default header values.
/*
GetAlertsBadRequest describes a response with status code 400, with default header values.
Bad request
*/
@@ -106,10 +135,39 @@ type GetAlertsBadRequest struct {
Payload string
}
// IsSuccess returns true when this get alerts bad request response has a 2xx status code
func (o *GetAlertsBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get alerts bad request response has a 3xx status code
func (o *GetAlertsBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this get alerts bad request response has a 4xx status code
func (o *GetAlertsBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this get alerts bad request response has a 5xx status code
func (o *GetAlertsBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this get alerts bad request response a status code equal to that given
func (o *GetAlertsBadRequest) IsCode(code int) bool {
return code == 400
}
func (o *GetAlertsBadRequest) Error() string {
return fmt.Sprintf("[GET /alerts][%d] getAlertsBadRequest %+v", 400, o.Payload)
}
func (o *GetAlertsBadRequest) String() string {
return fmt.Sprintf("[GET /alerts][%d] getAlertsBadRequest %+v", 400, o.Payload)
}
func (o *GetAlertsBadRequest) GetPayload() string {
return o.Payload
}
@@ -129,7 +187,8 @@ func NewGetAlertsInternalServerError() *GetAlertsInternalServerError {
return &GetAlertsInternalServerError{}
}
/*GetAlertsInternalServerError handles this case with default header values.
/*
GetAlertsInternalServerError describes a response with status code 500, with default header values.
Internal server error
*/
@@ -137,10 +196,39 @@ type GetAlertsInternalServerError struct {
Payload string
}
// IsSuccess returns true when this get alerts internal server error response has a 2xx status code
func (o *GetAlertsInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get alerts internal server error response has a 3xx status code
func (o *GetAlertsInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this get alerts internal server error response has a 4xx status code
func (o *GetAlertsInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this get alerts internal server error response has a 5xx status code
func (o *GetAlertsInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this get alerts internal server error response a status code equal to that given
func (o *GetAlertsInternalServerError) IsCode(code int) bool {
return code == 500
}
func (o *GetAlertsInternalServerError) Error() string {
return fmt.Sprintf("[GET /alerts][%d] getAlertsInternalServerError %+v", 500, o.Payload)
}
func (o *GetAlertsInternalServerError) String() string {
return fmt.Sprintf("[GET /alerts][%d] getAlertsInternalServerError %+v", 500, o.Payload)
}
func (o *GetAlertsInternalServerError) GetPayload() string {
return o.Payload
}

View File

@@ -27,59 +27,59 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// NewPostAlertsParams creates a new PostAlertsParams object
// with the default values initialized.
// NewPostAlertsParams creates a new PostAlertsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewPostAlertsParams() *PostAlertsParams {
var ()
return &PostAlertsParams{
timeout: cr.DefaultTimeout,
}
}
// NewPostAlertsParamsWithTimeout creates a new PostAlertsParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewPostAlertsParamsWithTimeout(timeout time.Duration) *PostAlertsParams {
var ()
return &PostAlertsParams{
timeout: timeout,
}
}
// NewPostAlertsParamsWithContext creates a new PostAlertsParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewPostAlertsParamsWithContext(ctx context.Context) *PostAlertsParams {
var ()
return &PostAlertsParams{
Context: ctx,
}
}
// NewPostAlertsParamsWithHTTPClient creates a new PostAlertsParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewPostAlertsParamsWithHTTPClient(client *http.Client) *PostAlertsParams {
var ()
return &PostAlertsParams{
HTTPClient: client,
}
}
/*PostAlertsParams contains all the parameters to send to the API endpoint
for the post alerts operation typically these are written to a http.Request
/*
PostAlertsParams contains all the parameters to send to the API endpoint
for the post alerts operation.
Typically these are written to a http.Request.
*/
type PostAlertsParams struct {
/*Alerts
The alerts to create
/* Alerts.
The alerts to create
*/
Alerts models.PostableAlerts
@@ -88,6 +88,21 @@ type PostAlertsParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the post alerts params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *PostAlertsParams) WithDefaults() *PostAlertsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the post alerts params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *PostAlertsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the post alerts params
func (o *PostAlertsParams) WithTimeout(timeout time.Duration) *PostAlertsParams {
o.SetTimeout(timeout)
@@ -139,7 +154,6 @@ func (o *PostAlertsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Re
return err
}
var res []error
if o.Alerts != nil {
if err := r.SetBodyParam(o.Alerts); err != nil {
return err

View File

@@ -24,8 +24,7 @@ import (
"io"
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// PostAlertsReader is a Reader for the PostAlerts structure.
@@ -54,9 +53,8 @@ func (o *PostAlertsReader) ReadResponse(response runtime.ClientResponse, consume
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -65,17 +63,47 @@ func NewPostAlertsOK() *PostAlertsOK {
return &PostAlertsOK{}
}
/*PostAlertsOK handles this case with default header values.
/*
PostAlertsOK describes a response with status code 200, with default header values.
Create alerts response
*/
type PostAlertsOK struct {
}
// IsSuccess returns true when this post alerts o k response has a 2xx status code
func (o *PostAlertsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this post alerts o k response has a 3xx status code
func (o *PostAlertsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this post alerts o k response has a 4xx status code
func (o *PostAlertsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this post alerts o k response has a 5xx status code
func (o *PostAlertsOK) IsServerError() bool {
return false
}
// IsCode returns true when this post alerts o k response a status code equal to that given
func (o *PostAlertsOK) IsCode(code int) bool {
return code == 200
}
func (o *PostAlertsOK) Error() string {
return fmt.Sprintf("[POST /alerts][%d] postAlertsOK ", 200)
}
func (o *PostAlertsOK) String() string {
return fmt.Sprintf("[POST /alerts][%d] postAlertsOK ", 200)
}
func (o *PostAlertsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
@@ -86,7 +114,8 @@ func NewPostAlertsBadRequest() *PostAlertsBadRequest {
return &PostAlertsBadRequest{}
}
/*PostAlertsBadRequest handles this case with default header values.
/*
PostAlertsBadRequest describes a response with status code 400, with default header values.
Bad request
*/
@@ -94,10 +123,39 @@ type PostAlertsBadRequest struct {
Payload string
}
// IsSuccess returns true when this post alerts bad request response has a 2xx status code
func (o *PostAlertsBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this post alerts bad request response has a 3xx status code
func (o *PostAlertsBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this post alerts bad request response has a 4xx status code
func (o *PostAlertsBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this post alerts bad request response has a 5xx status code
func (o *PostAlertsBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this post alerts bad request response a status code equal to that given
func (o *PostAlertsBadRequest) IsCode(code int) bool {
return code == 400
}
func (o *PostAlertsBadRequest) Error() string {
return fmt.Sprintf("[POST /alerts][%d] postAlertsBadRequest %+v", 400, o.Payload)
}
func (o *PostAlertsBadRequest) String() string {
return fmt.Sprintf("[POST /alerts][%d] postAlertsBadRequest %+v", 400, o.Payload)
}
func (o *PostAlertsBadRequest) GetPayload() string {
return o.Payload
}
@@ -117,7 +175,8 @@ func NewPostAlertsInternalServerError() *PostAlertsInternalServerError {
return &PostAlertsInternalServerError{}
}
/*PostAlertsInternalServerError handles this case with default header values.
/*
PostAlertsInternalServerError describes a response with status code 500, with default header values.
Internal server error
*/
@@ -125,10 +184,39 @@ type PostAlertsInternalServerError struct {
Payload string
}
// IsSuccess returns true when this post alerts internal server error response has a 2xx status code
func (o *PostAlertsInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this post alerts internal server error response has a 3xx status code
func (o *PostAlertsInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this post alerts internal server error response has a 4xx status code
func (o *PostAlertsInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this post alerts internal server error response has a 5xx status code
func (o *PostAlertsInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this post alerts internal server error response a status code equal to that given
func (o *PostAlertsInternalServerError) IsCode(code int) bool {
return code == 500
}
func (o *PostAlertsInternalServerError) Error() string {
return fmt.Sprintf("[POST /alerts][%d] postAlertsInternalServerError %+v", 500, o.Payload)
}
func (o *PostAlertsInternalServerError) String() string {
return fmt.Sprintf("[POST /alerts][%d] postAlertsInternalServerError %+v", 500, o.Payload)
}
func (o *PostAlertsInternalServerError) GetPayload() string {
return o.Payload
}

View File

@@ -23,12 +23,11 @@ import (
"fmt"
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// New creates a new alertgroup API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
@@ -40,16 +39,25 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
GetAlertGroups(params *GetAlertGroupsParams, opts ...ClientOption) (*GetAlertGroupsOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
GetAlertGroups Get a list of alert groups
*/
func (a *Client) GetAlertGroups(params *GetAlertGroupsParams) (*GetAlertGroupsOK, error) {
func (a *Client) GetAlertGroups(params *GetAlertGroupsParams, opts ...ClientOption) (*GetAlertGroupsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetAlertGroupsParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "getAlertGroups",
Method: "GET",
PathPattern: "/alerts/groups",
@@ -60,7 +68,12 @@ func (a *Client) GetAlertGroups(params *GetAlertGroupsParams) (*GetAlertGroupsOK
Reader: &GetAlertGroupsReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}

View File

@@ -27,106 +27,88 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
strfmt "github.com/go-openapi/strfmt"
)
// NewGetAlertGroupsParams creates a new GetAlertGroupsParams object
// with the default values initialized.
// NewGetAlertGroupsParams creates a new GetAlertGroupsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetAlertGroupsParams() *GetAlertGroupsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
)
return &GetAlertGroupsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
timeout: cr.DefaultTimeout,
}
}
// NewGetAlertGroupsParamsWithTimeout creates a new GetAlertGroupsParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewGetAlertGroupsParamsWithTimeout(timeout time.Duration) *GetAlertGroupsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
)
return &GetAlertGroupsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
timeout: timeout,
}
}
// NewGetAlertGroupsParamsWithContext creates a new GetAlertGroupsParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewGetAlertGroupsParamsWithContext(ctx context.Context) *GetAlertGroupsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
)
return &GetAlertGroupsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
Context: ctx,
}
}
// NewGetAlertGroupsParamsWithHTTPClient creates a new GetAlertGroupsParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewGetAlertGroupsParamsWithHTTPClient(client *http.Client) *GetAlertGroupsParams {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
)
return &GetAlertGroupsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
HTTPClient: client,
}
}
/*GetAlertGroupsParams contains all the parameters to send to the API endpoint
for the get alert groups operation typically these are written to a http.Request
/*
GetAlertGroupsParams contains all the parameters to send to the API endpoint
for the get alert groups operation.
Typically these are written to a http.Request.
*/
type GetAlertGroupsParams struct {
/*Active
Show active alerts
/* Active.
Show active alerts
Default: true
*/
Active *bool
/*Filter
A list of matchers to filter alerts by
/* Filter.
A list of matchers to filter alerts by
*/
Filter []string
/*Inhibited
Show inhibited alerts
/* Inhibited.
Show inhibited alerts
Default: true
*/
Inhibited *bool
/*Receiver
A regex matching receivers to filter alerts by
/* Receiver.
A regex matching receivers to filter alerts by
*/
Receiver *string
/*Silenced
Show silenced alerts
/* Silenced.
Show silenced alerts
Default: true
*/
Silenced *bool
@@ -135,6 +117,38 @@ type GetAlertGroupsParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get alert groups params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetAlertGroupsParams) WithDefaults() *GetAlertGroupsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get alert groups params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetAlertGroupsParams) SetDefaults() {
var (
activeDefault = bool(true)
inhibitedDefault = bool(true)
silencedDefault = bool(true)
)
val := GetAlertGroupsParams{
Active: &activeDefault,
Inhibited: &inhibitedDefault,
Silenced: &silencedDefault,
}
val.timeout = o.timeout
val.Context = o.Context
val.HTTPClient = o.HTTPClient
*o = val
}
// WithTimeout adds the timeout to the get alert groups params
func (o *GetAlertGroupsParams) WithTimeout(timeout time.Duration) *GetAlertGroupsParams {
o.SetTimeout(timeout)
@@ -235,72 +249,79 @@ func (o *GetAlertGroupsParams) WriteToRequest(r runtime.ClientRequest, reg strfm
// query param active
var qrActive bool
if o.Active != nil {
qrActive = *o.Active
}
qActive := swag.FormatBool(qrActive)
if qActive != "" {
if err := r.SetQueryParam("active", qActive); err != nil {
return err
}
}
}
valuesFilter := o.Filter
if o.Filter != nil {
joinedFilter := swag.JoinByFormat(valuesFilter, "multi")
// query array param filter
if err := r.SetQueryParam("filter", joinedFilter...); err != nil {
return err
// binding items for filter
joinedFilter := o.bindParamFilter(reg)
// query array param filter
if err := r.SetQueryParam("filter", joinedFilter...); err != nil {
return err
}
}
if o.Inhibited != nil {
// query param inhibited
var qrInhibited bool
if o.Inhibited != nil {
qrInhibited = *o.Inhibited
}
qInhibited := swag.FormatBool(qrInhibited)
if qInhibited != "" {
if err := r.SetQueryParam("inhibited", qInhibited); err != nil {
return err
}
}
}
if o.Receiver != nil {
// query param receiver
var qrReceiver string
if o.Receiver != nil {
qrReceiver = *o.Receiver
}
qReceiver := qrReceiver
if qReceiver != "" {
if err := r.SetQueryParam("receiver", qReceiver); err != nil {
return err
}
}
}
if o.Silenced != nil {
// query param silenced
var qrSilenced bool
if o.Silenced != nil {
qrSilenced = *o.Silenced
}
qSilenced := swag.FormatBool(qrSilenced)
if qSilenced != "" {
if err := r.SetQueryParam("silenced", qSilenced); err != nil {
return err
}
}
}
if len(res) > 0 {
@@ -308,3 +329,20 @@ func (o *GetAlertGroupsParams) WriteToRequest(r runtime.ClientRequest, reg strfm
}
return nil
}
// bindParamGetAlertGroups binds the parameter filter
func (o *GetAlertGroupsParams) bindParamFilter(formats strfmt.Registry) []string {
filterIR := o.Filter
var filterIC []string
for _, filterIIR := range filterIR { // explode []string
filterIIV := filterIIR // string as string
filterIC = append(filterIC, filterIIV)
}
// items.CollectionFormat: "multi"
filterIS := swag.JoinByFormat(filterIC, "multi")
return filterIS
}

View File

@@ -24,10 +24,9 @@ import (
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// GetAlertGroupsReader is a Reader for the GetAlertGroups structure.
@@ -56,9 +55,8 @@ func (o *GetAlertGroupsReader) ReadResponse(response runtime.ClientResponse, con
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -67,7 +65,8 @@ func NewGetAlertGroupsOK() *GetAlertGroupsOK {
return &GetAlertGroupsOK{}
}
/*GetAlertGroupsOK handles this case with default header values.
/*
GetAlertGroupsOK describes a response with status code 200, with default header values.
Get alert groups response
*/
@@ -75,10 +74,39 @@ type GetAlertGroupsOK struct {
Payload models.AlertGroups
}
// IsSuccess returns true when this get alert groups o k response has a 2xx status code
func (o *GetAlertGroupsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get alert groups o k response has a 3xx status code
func (o *GetAlertGroupsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get alert groups o k response has a 4xx status code
func (o *GetAlertGroupsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get alert groups o k response has a 5xx status code
func (o *GetAlertGroupsOK) IsServerError() bool {
return false
}
// IsCode returns true when this get alert groups o k response a status code equal to that given
func (o *GetAlertGroupsOK) IsCode(code int) bool {
return code == 200
}
func (o *GetAlertGroupsOK) Error() string {
return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsOK %+v", 200, o.Payload)
}
func (o *GetAlertGroupsOK) String() string {
return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsOK %+v", 200, o.Payload)
}
func (o *GetAlertGroupsOK) GetPayload() models.AlertGroups {
return o.Payload
}
@@ -98,7 +126,8 @@ func NewGetAlertGroupsBadRequest() *GetAlertGroupsBadRequest {
return &GetAlertGroupsBadRequest{}
}
/*GetAlertGroupsBadRequest handles this case with default header values.
/*
GetAlertGroupsBadRequest describes a response with status code 400, with default header values.
Bad request
*/
@@ -106,10 +135,39 @@ type GetAlertGroupsBadRequest struct {
Payload string
}
// IsSuccess returns true when this get alert groups bad request response has a 2xx status code
func (o *GetAlertGroupsBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get alert groups bad request response has a 3xx status code
func (o *GetAlertGroupsBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this get alert groups bad request response has a 4xx status code
func (o *GetAlertGroupsBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this get alert groups bad request response has a 5xx status code
func (o *GetAlertGroupsBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this get alert groups bad request response a status code equal to that given
func (o *GetAlertGroupsBadRequest) IsCode(code int) bool {
return code == 400
}
func (o *GetAlertGroupsBadRequest) Error() string {
return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsBadRequest %+v", 400, o.Payload)
}
func (o *GetAlertGroupsBadRequest) String() string {
return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsBadRequest %+v", 400, o.Payload)
}
func (o *GetAlertGroupsBadRequest) GetPayload() string {
return o.Payload
}
@@ -129,7 +187,8 @@ func NewGetAlertGroupsInternalServerError() *GetAlertGroupsInternalServerError {
return &GetAlertGroupsInternalServerError{}
}
/*GetAlertGroupsInternalServerError handles this case with default header values.
/*
GetAlertGroupsInternalServerError describes a response with status code 500, with default header values.
Internal server error
*/
@@ -137,10 +196,39 @@ type GetAlertGroupsInternalServerError struct {
Payload string
}
// IsSuccess returns true when this get alert groups internal server error response has a 2xx status code
func (o *GetAlertGroupsInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get alert groups internal server error response has a 3xx status code
func (o *GetAlertGroupsInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this get alert groups internal server error response has a 4xx status code
func (o *GetAlertGroupsInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this get alert groups internal server error response has a 5xx status code
func (o *GetAlertGroupsInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this get alert groups internal server error response a status code equal to that given
func (o *GetAlertGroupsInternalServerError) IsCode(code int) bool {
return code == 500
}
func (o *GetAlertGroupsInternalServerError) Error() string {
return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsInternalServerError %+v", 500, o.Payload)
}
func (o *GetAlertGroupsInternalServerError) String() string {
return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsInternalServerError %+v", 500, o.Payload)
}
func (o *GetAlertGroupsInternalServerError) GetPayload() string {
return o.Payload
}

View File

@@ -22,8 +22,7 @@ package client
import (
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
"github.com/prometheus/alertmanager/api/v2/client/alert"
"github.com/prometheus/alertmanager/api/v2/client/alertgroup"
@@ -32,7 +31,7 @@ import (
"github.com/prometheus/alertmanager/api/v2/client/silence"
)
// Default alertmanager HTTP client.
// Default alertmanager API HTTP client.
var Default = NewHTTPClient(nil)
const (
@@ -41,20 +40,20 @@ const (
DefaultHost string = "localhost"
// DefaultBasePath is the default BasePath
// found in Meta (info) section of spec file
DefaultBasePath string = "/"
DefaultBasePath string = "/api/v2/"
)
// DefaultSchemes are the default schemes found in Meta (info) section of spec file
var DefaultSchemes = []string{"http"}
// NewHTTPClient creates a new alertmanager HTTP client.
func NewHTTPClient(formats strfmt.Registry) *Alertmanager {
// NewHTTPClient creates a new alertmanager API HTTP client.
func NewHTTPClient(formats strfmt.Registry) *AlertmanagerAPI {
return NewHTTPClientWithConfig(formats, nil)
}
// NewHTTPClientWithConfig creates a new alertmanager HTTP client,
// NewHTTPClientWithConfig creates a new alertmanager API HTTP client,
// using a customizable transport config.
func NewHTTPClientWithConfig(formats strfmt.Registry, cfg *TransportConfig) *Alertmanager {
func NewHTTPClientWithConfig(formats strfmt.Registry, cfg *TransportConfig) *AlertmanagerAPI {
// ensure nullable parameters have default
if cfg == nil {
cfg = DefaultTransportConfig()
@@ -65,26 +64,20 @@ func NewHTTPClientWithConfig(formats strfmt.Registry, cfg *TransportConfig) *Ale
return New(transport, formats)
}
// New creates a new alertmanager client
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Alertmanager {
// New creates a new alertmanager API client
func New(transport runtime.ClientTransport, formats strfmt.Registry) *AlertmanagerAPI {
// ensure nullable parameters have default
if formats == nil {
formats = strfmt.Default
}
cli := new(Alertmanager)
cli := new(AlertmanagerAPI)
cli.Transport = transport
cli.Alert = alert.New(transport, formats)
cli.Alertgroup = alertgroup.New(transport, formats)
cli.General = general.New(transport, formats)
cli.Receiver = receiver.New(transport, formats)
cli.Silence = silence.New(transport, formats)
return cli
}
@@ -127,33 +120,27 @@ func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig {
return cfg
}
// Alertmanager is a client for alertmanager
type Alertmanager struct {
Alert *alert.Client
// AlertmanagerAPI is a client for alertmanager API
type AlertmanagerAPI struct {
Alert alert.ClientService
Alertgroup *alertgroup.Client
Alertgroup alertgroup.ClientService
General *general.Client
General general.ClientService
Receiver *receiver.Client
Receiver receiver.ClientService
Silence *silence.Client
Silence silence.ClientService
Transport runtime.ClientTransport
}
// SetTransport changes the transport on the client and all its subresources
func (c *Alertmanager) SetTransport(transport runtime.ClientTransport) {
func (c *AlertmanagerAPI) SetTransport(transport runtime.ClientTransport) {
c.Transport = transport
c.Alert.SetTransport(transport)
c.Alertgroup.SetTransport(transport)
c.General.SetTransport(transport)
c.Receiver.SetTransport(transport)
c.Silence.SetTransport(transport)
}

View File

@@ -23,12 +23,11 @@ import (
"fmt"
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// New creates a new general API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
@@ -40,16 +39,25 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
GetStatus(params *GetStatusParams, opts ...ClientOption) (*GetStatusOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
GetStatus Get current status of an Alertmanager instance and its cluster
*/
func (a *Client) GetStatus(params *GetStatusParams) (*GetStatusOK, error) {
func (a *Client) GetStatus(params *GetStatusParams, opts ...ClientOption) (*GetStatusOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetStatusParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "getStatus",
Method: "GET",
PathPattern: "/status",
@@ -60,7 +68,12 @@ func (a *Client) GetStatus(params *GetStatusParams) (*GetStatusOK, error) {
Reader: &GetStatusReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}

View File

@@ -27,51 +27,51 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// NewGetStatusParams creates a new GetStatusParams object
// with the default values initialized.
// NewGetStatusParams creates a new GetStatusParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetStatusParams() *GetStatusParams {
return &GetStatusParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetStatusParamsWithTimeout creates a new GetStatusParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewGetStatusParamsWithTimeout(timeout time.Duration) *GetStatusParams {
return &GetStatusParams{
timeout: timeout,
}
}
// NewGetStatusParamsWithContext creates a new GetStatusParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewGetStatusParamsWithContext(ctx context.Context) *GetStatusParams {
return &GetStatusParams{
Context: ctx,
}
}
// NewGetStatusParamsWithHTTPClient creates a new GetStatusParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewGetStatusParamsWithHTTPClient(client *http.Client) *GetStatusParams {
return &GetStatusParams{
HTTPClient: client,
}
}
/*GetStatusParams contains all the parameters to send to the API endpoint
for the get status operation typically these are written to a http.Request
/*
GetStatusParams contains all the parameters to send to the API endpoint
for the get status operation.
Typically these are written to a http.Request.
*/
type GetStatusParams struct {
timeout time.Duration
@@ -79,6 +79,21 @@ type GetStatusParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get status params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetStatusParams) WithDefaults() *GetStatusParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get status params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetStatusParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get status params
func (o *GetStatusParams) WithTimeout(timeout time.Duration) *GetStatusParams {
o.SetTimeout(timeout)

View File

@@ -24,10 +24,9 @@ import (
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// GetStatusReader is a Reader for the GetStatus structure.
@@ -44,9 +43,8 @@ func (o *GetStatusReader) ReadResponse(response runtime.ClientResponse, consumer
return nil, err
}
return result, nil
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -55,7 +53,8 @@ func NewGetStatusOK() *GetStatusOK {
return &GetStatusOK{}
}
/*GetStatusOK handles this case with default header values.
/*
GetStatusOK describes a response with status code 200, with default header values.
Get status response
*/
@@ -63,10 +62,39 @@ type GetStatusOK struct {
Payload *models.AlertmanagerStatus
}
// IsSuccess returns true when this get status o k response has a 2xx status code
func (o *GetStatusOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get status o k response has a 3xx status code
func (o *GetStatusOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get status o k response has a 4xx status code
func (o *GetStatusOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get status o k response has a 5xx status code
func (o *GetStatusOK) IsServerError() bool {
return false
}
// IsCode returns true when this get status o k response a status code equal to that given
func (o *GetStatusOK) IsCode(code int) bool {
return code == 200
}
func (o *GetStatusOK) Error() string {
return fmt.Sprintf("[GET /status][%d] getStatusOK %+v", 200, o.Payload)
}
func (o *GetStatusOK) String() string {
return fmt.Sprintf("[GET /status][%d] getStatusOK %+v", 200, o.Payload)
}
func (o *GetStatusOK) GetPayload() *models.AlertmanagerStatus {
return o.Payload
}

View File

@@ -27,51 +27,51 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// NewGetReceiversParams creates a new GetReceiversParams object
// with the default values initialized.
// NewGetReceiversParams creates a new GetReceiversParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetReceiversParams() *GetReceiversParams {
return &GetReceiversParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetReceiversParamsWithTimeout creates a new GetReceiversParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewGetReceiversParamsWithTimeout(timeout time.Duration) *GetReceiversParams {
return &GetReceiversParams{
timeout: timeout,
}
}
// NewGetReceiversParamsWithContext creates a new GetReceiversParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewGetReceiversParamsWithContext(ctx context.Context) *GetReceiversParams {
return &GetReceiversParams{
Context: ctx,
}
}
// NewGetReceiversParamsWithHTTPClient creates a new GetReceiversParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewGetReceiversParamsWithHTTPClient(client *http.Client) *GetReceiversParams {
return &GetReceiversParams{
HTTPClient: client,
}
}
/*GetReceiversParams contains all the parameters to send to the API endpoint
for the get receivers operation typically these are written to a http.Request
/*
GetReceiversParams contains all the parameters to send to the API endpoint
for the get receivers operation.
Typically these are written to a http.Request.
*/
type GetReceiversParams struct {
timeout time.Duration
@@ -79,6 +79,21 @@ type GetReceiversParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get receivers params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetReceiversParams) WithDefaults() *GetReceiversParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get receivers params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetReceiversParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get receivers params
func (o *GetReceiversParams) WithTimeout(timeout time.Duration) *GetReceiversParams {
o.SetTimeout(timeout)

View File

@@ -24,10 +24,9 @@ import (
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// GetReceiversReader is a Reader for the GetReceivers structure.
@@ -44,9 +43,8 @@ func (o *GetReceiversReader) ReadResponse(response runtime.ClientResponse, consu
return nil, err
}
return result, nil
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -55,7 +53,8 @@ func NewGetReceiversOK() *GetReceiversOK {
return &GetReceiversOK{}
}
/*GetReceiversOK handles this case with default header values.
/*
GetReceiversOK describes a response with status code 200, with default header values.
Get receivers response
*/
@@ -63,10 +62,39 @@ type GetReceiversOK struct {
Payload []*models.Receiver
}
// IsSuccess returns true when this get receivers o k response has a 2xx status code
func (o *GetReceiversOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get receivers o k response has a 3xx status code
func (o *GetReceiversOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get receivers o k response has a 4xx status code
func (o *GetReceiversOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get receivers o k response has a 5xx status code
func (o *GetReceiversOK) IsServerError() bool {
return false
}
// IsCode returns true when this get receivers o k response a status code equal to that given
func (o *GetReceiversOK) IsCode(code int) bool {
return code == 200
}
func (o *GetReceiversOK) Error() string {
return fmt.Sprintf("[GET /receivers][%d] getReceiversOK %+v", 200, o.Payload)
}
func (o *GetReceiversOK) String() string {
return fmt.Sprintf("[GET /receivers][%d] getReceiversOK %+v", 200, o.Payload)
}
func (o *GetReceiversOK) GetPayload() []*models.Receiver {
return o.Payload
}

View File

@@ -23,12 +23,11 @@ import (
"fmt"
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// New creates a new receiver API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
@@ -40,16 +39,25 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
GetReceivers(params *GetReceiversParams, opts ...ClientOption) (*GetReceiversOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
GetReceivers Get list of all receivers (name of notification integrations)
*/
func (a *Client) GetReceivers(params *GetReceiversParams) (*GetReceiversOK, error) {
func (a *Client) GetReceivers(params *GetReceiversParams, opts ...ClientOption) (*GetReceiversOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetReceiversParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "getReceivers",
Method: "GET",
PathPattern: "/receivers",
@@ -60,7 +68,12 @@ func (a *Client) GetReceivers(params *GetReceiversParams) (*GetReceiversOK, erro
Reader: &GetReceiversReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}

View File

@@ -27,57 +27,59 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// NewDeleteSilenceParams creates a new DeleteSilenceParams object
// with the default values initialized.
// NewDeleteSilenceParams creates a new DeleteSilenceParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewDeleteSilenceParams() *DeleteSilenceParams {
var ()
return &DeleteSilenceParams{
timeout: cr.DefaultTimeout,
}
}
// NewDeleteSilenceParamsWithTimeout creates a new DeleteSilenceParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewDeleteSilenceParamsWithTimeout(timeout time.Duration) *DeleteSilenceParams {
var ()
return &DeleteSilenceParams{
timeout: timeout,
}
}
// NewDeleteSilenceParamsWithContext creates a new DeleteSilenceParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewDeleteSilenceParamsWithContext(ctx context.Context) *DeleteSilenceParams {
var ()
return &DeleteSilenceParams{
Context: ctx,
}
}
// NewDeleteSilenceParamsWithHTTPClient creates a new DeleteSilenceParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewDeleteSilenceParamsWithHTTPClient(client *http.Client) *DeleteSilenceParams {
var ()
return &DeleteSilenceParams{
HTTPClient: client,
}
}
/*DeleteSilenceParams contains all the parameters to send to the API endpoint
for the delete silence operation typically these are written to a http.Request
/*
DeleteSilenceParams contains all the parameters to send to the API endpoint
for the delete silence operation.
Typically these are written to a http.Request.
*/
type DeleteSilenceParams struct {
/*SilenceID
ID of the silence to get
/* SilenceID.
ID of the silence to get
Format: uuid
*/
SilenceID strfmt.UUID
@@ -86,6 +88,21 @@ type DeleteSilenceParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the delete silence params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteSilenceParams) WithDefaults() *DeleteSilenceParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the delete silence params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteSilenceParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the delete silence params
func (o *DeleteSilenceParams) WithTimeout(timeout time.Duration) *DeleteSilenceParams {
o.SetTimeout(timeout)

View File

@@ -24,8 +24,7 @@ import (
"io"
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// DeleteSilenceReader is a Reader for the DeleteSilence structure.
@@ -48,9 +47,8 @@ func (o *DeleteSilenceReader) ReadResponse(response runtime.ClientResponse, cons
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -59,17 +57,47 @@ func NewDeleteSilenceOK() *DeleteSilenceOK {
return &DeleteSilenceOK{}
}
/*DeleteSilenceOK handles this case with default header values.
/*
DeleteSilenceOK describes a response with status code 200, with default header values.
Delete silence response
*/
type DeleteSilenceOK struct {
}
// IsSuccess returns true when this delete silence o k response has a 2xx status code
func (o *DeleteSilenceOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this delete silence o k response has a 3xx status code
func (o *DeleteSilenceOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this delete silence o k response has a 4xx status code
func (o *DeleteSilenceOK) IsClientError() bool {
return false
}
// IsServerError returns true when this delete silence o k response has a 5xx status code
func (o *DeleteSilenceOK) IsServerError() bool {
return false
}
// IsCode returns true when this delete silence o k response a status code equal to that given
func (o *DeleteSilenceOK) IsCode(code int) bool {
return code == 200
}
func (o *DeleteSilenceOK) Error() string {
return fmt.Sprintf("[DELETE /silence/{silenceID}][%d] deleteSilenceOK ", 200)
}
func (o *DeleteSilenceOK) String() string {
return fmt.Sprintf("[DELETE /silence/{silenceID}][%d] deleteSilenceOK ", 200)
}
func (o *DeleteSilenceOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
@@ -80,7 +108,8 @@ func NewDeleteSilenceInternalServerError() *DeleteSilenceInternalServerError {
return &DeleteSilenceInternalServerError{}
}
/*DeleteSilenceInternalServerError handles this case with default header values.
/*
DeleteSilenceInternalServerError describes a response with status code 500, with default header values.
Internal server error
*/
@@ -88,10 +117,39 @@ type DeleteSilenceInternalServerError struct {
Payload string
}
// IsSuccess returns true when this delete silence internal server error response has a 2xx status code
func (o *DeleteSilenceInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this delete silence internal server error response has a 3xx status code
func (o *DeleteSilenceInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this delete silence internal server error response has a 4xx status code
func (o *DeleteSilenceInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this delete silence internal server error response has a 5xx status code
func (o *DeleteSilenceInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this delete silence internal server error response a status code equal to that given
func (o *DeleteSilenceInternalServerError) IsCode(code int) bool {
return code == 500
}
func (o *DeleteSilenceInternalServerError) Error() string {
return fmt.Sprintf("[DELETE /silence/{silenceID}][%d] deleteSilenceInternalServerError %+v", 500, o.Payload)
}
func (o *DeleteSilenceInternalServerError) String() string {
return fmt.Sprintf("[DELETE /silence/{silenceID}][%d] deleteSilenceInternalServerError %+v", 500, o.Payload)
}
func (o *DeleteSilenceInternalServerError) GetPayload() string {
return o.Payload
}

View File

@@ -27,57 +27,59 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// NewGetSilenceParams creates a new GetSilenceParams object
// with the default values initialized.
// NewGetSilenceParams creates a new GetSilenceParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetSilenceParams() *GetSilenceParams {
var ()
return &GetSilenceParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetSilenceParamsWithTimeout creates a new GetSilenceParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewGetSilenceParamsWithTimeout(timeout time.Duration) *GetSilenceParams {
var ()
return &GetSilenceParams{
timeout: timeout,
}
}
// NewGetSilenceParamsWithContext creates a new GetSilenceParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewGetSilenceParamsWithContext(ctx context.Context) *GetSilenceParams {
var ()
return &GetSilenceParams{
Context: ctx,
}
}
// NewGetSilenceParamsWithHTTPClient creates a new GetSilenceParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewGetSilenceParamsWithHTTPClient(client *http.Client) *GetSilenceParams {
var ()
return &GetSilenceParams{
HTTPClient: client,
}
}
/*GetSilenceParams contains all the parameters to send to the API endpoint
for the get silence operation typically these are written to a http.Request
/*
GetSilenceParams contains all the parameters to send to the API endpoint
for the get silence operation.
Typically these are written to a http.Request.
*/
type GetSilenceParams struct {
/*SilenceID
ID of the silence to get
/* SilenceID.
ID of the silence to get
Format: uuid
*/
SilenceID strfmt.UUID
@@ -86,6 +88,21 @@ type GetSilenceParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get silence params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetSilenceParams) WithDefaults() *GetSilenceParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get silence params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetSilenceParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get silence params
func (o *GetSilenceParams) WithTimeout(timeout time.Duration) *GetSilenceParams {
o.SetTimeout(timeout)

View File

@@ -24,10 +24,9 @@ import (
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// GetSilenceReader is a Reader for the GetSilence structure.
@@ -56,9 +55,8 @@ func (o *GetSilenceReader) ReadResponse(response runtime.ClientResponse, consume
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -67,7 +65,8 @@ func NewGetSilenceOK() *GetSilenceOK {
return &GetSilenceOK{}
}
/*GetSilenceOK handles this case with default header values.
/*
GetSilenceOK describes a response with status code 200, with default header values.
Get silence response
*/
@@ -75,10 +74,39 @@ type GetSilenceOK struct {
Payload *models.GettableSilence
}
// IsSuccess returns true when this get silence o k response has a 2xx status code
func (o *GetSilenceOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get silence o k response has a 3xx status code
func (o *GetSilenceOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get silence o k response has a 4xx status code
func (o *GetSilenceOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get silence o k response has a 5xx status code
func (o *GetSilenceOK) IsServerError() bool {
return false
}
// IsCode returns true when this get silence o k response a status code equal to that given
func (o *GetSilenceOK) IsCode(code int) bool {
return code == 200
}
func (o *GetSilenceOK) Error() string {
return fmt.Sprintf("[GET /silence/{silenceID}][%d] getSilenceOK %+v", 200, o.Payload)
}
func (o *GetSilenceOK) String() string {
return fmt.Sprintf("[GET /silence/{silenceID}][%d] getSilenceOK %+v", 200, o.Payload)
}
func (o *GetSilenceOK) GetPayload() *models.GettableSilence {
return o.Payload
}
@@ -100,17 +128,47 @@ func NewGetSilenceNotFound() *GetSilenceNotFound {
return &GetSilenceNotFound{}
}
/*GetSilenceNotFound handles this case with default header values.
/*
GetSilenceNotFound describes a response with status code 404, with default header values.
A silence with the specified ID was not found
*/
type GetSilenceNotFound struct {
}
// IsSuccess returns true when this get silence not found response has a 2xx status code
func (o *GetSilenceNotFound) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get silence not found response has a 3xx status code
func (o *GetSilenceNotFound) IsRedirect() bool {
return false
}
// IsClientError returns true when this get silence not found response has a 4xx status code
func (o *GetSilenceNotFound) IsClientError() bool {
return true
}
// IsServerError returns true when this get silence not found response has a 5xx status code
func (o *GetSilenceNotFound) IsServerError() bool {
return false
}
// IsCode returns true when this get silence not found response a status code equal to that given
func (o *GetSilenceNotFound) IsCode(code int) bool {
return code == 404
}
func (o *GetSilenceNotFound) Error() string {
return fmt.Sprintf("[GET /silence/{silenceID}][%d] getSilenceNotFound ", 404)
}
func (o *GetSilenceNotFound) String() string {
return fmt.Sprintf("[GET /silence/{silenceID}][%d] getSilenceNotFound ", 404)
}
func (o *GetSilenceNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
@@ -121,7 +179,8 @@ func NewGetSilenceInternalServerError() *GetSilenceInternalServerError {
return &GetSilenceInternalServerError{}
}
/*GetSilenceInternalServerError handles this case with default header values.
/*
GetSilenceInternalServerError describes a response with status code 500, with default header values.
Internal server error
*/
@@ -129,10 +188,39 @@ type GetSilenceInternalServerError struct {
Payload string
}
// IsSuccess returns true when this get silence internal server error response has a 2xx status code
func (o *GetSilenceInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get silence internal server error response has a 3xx status code
func (o *GetSilenceInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this get silence internal server error response has a 4xx status code
func (o *GetSilenceInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this get silence internal server error response has a 5xx status code
func (o *GetSilenceInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this get silence internal server error response a status code equal to that given
func (o *GetSilenceInternalServerError) IsCode(code int) bool {
return code == 500
}
func (o *GetSilenceInternalServerError) Error() string {
return fmt.Sprintf("[GET /silence/{silenceID}][%d] getSilenceInternalServerError %+v", 500, o.Payload)
}
func (o *GetSilenceInternalServerError) String() string {
return fmt.Sprintf("[GET /silence/{silenceID}][%d] getSilenceInternalServerError %+v", 500, o.Payload)
}
func (o *GetSilenceInternalServerError) GetPayload() string {
return o.Payload
}

View File

@@ -27,58 +27,58 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
strfmt "github.com/go-openapi/strfmt"
)
// NewGetSilencesParams creates a new GetSilencesParams object
// with the default values initialized.
// NewGetSilencesParams creates a new GetSilencesParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetSilencesParams() *GetSilencesParams {
var ()
return &GetSilencesParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetSilencesParamsWithTimeout creates a new GetSilencesParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewGetSilencesParamsWithTimeout(timeout time.Duration) *GetSilencesParams {
var ()
return &GetSilencesParams{
timeout: timeout,
}
}
// NewGetSilencesParamsWithContext creates a new GetSilencesParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewGetSilencesParamsWithContext(ctx context.Context) *GetSilencesParams {
var ()
return &GetSilencesParams{
Context: ctx,
}
}
// NewGetSilencesParamsWithHTTPClient creates a new GetSilencesParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewGetSilencesParamsWithHTTPClient(client *http.Client) *GetSilencesParams {
var ()
return &GetSilencesParams{
HTTPClient: client,
}
}
/*GetSilencesParams contains all the parameters to send to the API endpoint
for the get silences operation typically these are written to a http.Request
/*
GetSilencesParams contains all the parameters to send to the API endpoint
for the get silences operation.
Typically these are written to a http.Request.
*/
type GetSilencesParams struct {
/*Filter
A list of matchers to filter silences by
/* Filter.
A list of matchers to filter silences by
*/
Filter []string
@@ -87,6 +87,21 @@ type GetSilencesParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get silences params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetSilencesParams) WithDefaults() *GetSilencesParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get silences params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetSilencesParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get silences params
func (o *GetSilencesParams) WithTimeout(timeout time.Duration) *GetSilencesParams {
o.SetTimeout(timeout)
@@ -139,12 +154,15 @@ func (o *GetSilencesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.R
}
var res []error
valuesFilter := o.Filter
if o.Filter != nil {
joinedFilter := swag.JoinByFormat(valuesFilter, "multi")
// query array param filter
if err := r.SetQueryParam("filter", joinedFilter...); err != nil {
return err
// binding items for filter
joinedFilter := o.bindParamFilter(reg)
// query array param filter
if err := r.SetQueryParam("filter", joinedFilter...); err != nil {
return err
}
}
if len(res) > 0 {
@@ -152,3 +170,20 @@ func (o *GetSilencesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.R
}
return nil
}
// bindParamGetSilences binds the parameter filter
func (o *GetSilencesParams) bindParamFilter(formats strfmt.Registry) []string {
filterIR := o.Filter
var filterIC []string
for _, filterIIR := range filterIR { // explode []string
filterIIV := filterIIR // string as string
filterIC = append(filterIC, filterIIV)
}
// items.CollectionFormat: "multi"
filterIS := swag.JoinByFormat(filterIC, "multi")
return filterIS
}

View File

@@ -24,10 +24,9 @@ import (
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// GetSilencesReader is a Reader for the GetSilences structure.
@@ -50,9 +49,8 @@ func (o *GetSilencesReader) ReadResponse(response runtime.ClientResponse, consum
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -61,7 +59,8 @@ func NewGetSilencesOK() *GetSilencesOK {
return &GetSilencesOK{}
}
/*GetSilencesOK handles this case with default header values.
/*
GetSilencesOK describes a response with status code 200, with default header values.
Get silences response
*/
@@ -69,10 +68,39 @@ type GetSilencesOK struct {
Payload models.GettableSilences
}
// IsSuccess returns true when this get silences o k response has a 2xx status code
func (o *GetSilencesOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get silences o k response has a 3xx status code
func (o *GetSilencesOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get silences o k response has a 4xx status code
func (o *GetSilencesOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get silences o k response has a 5xx status code
func (o *GetSilencesOK) IsServerError() bool {
return false
}
// IsCode returns true when this get silences o k response a status code equal to that given
func (o *GetSilencesOK) IsCode(code int) bool {
return code == 200
}
func (o *GetSilencesOK) Error() string {
return fmt.Sprintf("[GET /silences][%d] getSilencesOK %+v", 200, o.Payload)
}
func (o *GetSilencesOK) String() string {
return fmt.Sprintf("[GET /silences][%d] getSilencesOK %+v", 200, o.Payload)
}
func (o *GetSilencesOK) GetPayload() models.GettableSilences {
return o.Payload
}
@@ -92,7 +120,8 @@ func NewGetSilencesInternalServerError() *GetSilencesInternalServerError {
return &GetSilencesInternalServerError{}
}
/*GetSilencesInternalServerError handles this case with default header values.
/*
GetSilencesInternalServerError describes a response with status code 500, with default header values.
Internal server error
*/
@@ -100,10 +129,39 @@ type GetSilencesInternalServerError struct {
Payload string
}
// IsSuccess returns true when this get silences internal server error response has a 2xx status code
func (o *GetSilencesInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get silences internal server error response has a 3xx status code
func (o *GetSilencesInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this get silences internal server error response has a 4xx status code
func (o *GetSilencesInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this get silences internal server error response has a 5xx status code
func (o *GetSilencesInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this get silences internal server error response a status code equal to that given
func (o *GetSilencesInternalServerError) IsCode(code int) bool {
return code == 500
}
func (o *GetSilencesInternalServerError) Error() string {
return fmt.Sprintf("[GET /silences][%d] getSilencesInternalServerError %+v", 500, o.Payload)
}
func (o *GetSilencesInternalServerError) String() string {
return fmt.Sprintf("[GET /silences][%d] getSilencesInternalServerError %+v", 500, o.Payload)
}
func (o *GetSilencesInternalServerError) GetPayload() string {
return o.Payload
}

View File

@@ -27,59 +27,59 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
strfmt "github.com/go-openapi/strfmt"
models "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/api/v2/models"
)
// NewPostSilencesParams creates a new PostSilencesParams object
// with the default values initialized.
// NewPostSilencesParams creates a new PostSilencesParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewPostSilencesParams() *PostSilencesParams {
var ()
return &PostSilencesParams{
timeout: cr.DefaultTimeout,
}
}
// NewPostSilencesParamsWithTimeout creates a new PostSilencesParams object
// with the default values initialized, and the ability to set a timeout on a request
// with the ability to set a timeout on a request.
func NewPostSilencesParamsWithTimeout(timeout time.Duration) *PostSilencesParams {
var ()
return &PostSilencesParams{
timeout: timeout,
}
}
// NewPostSilencesParamsWithContext creates a new PostSilencesParams object
// with the default values initialized, and the ability to set a context for a request
// with the ability to set a context for a request.
func NewPostSilencesParamsWithContext(ctx context.Context) *PostSilencesParams {
var ()
return &PostSilencesParams{
Context: ctx,
}
}
// NewPostSilencesParamsWithHTTPClient creates a new PostSilencesParams object
// with the default values initialized, and the ability to set a custom HTTPClient for a request
// with the ability to set a custom HTTPClient for a request.
func NewPostSilencesParamsWithHTTPClient(client *http.Client) *PostSilencesParams {
var ()
return &PostSilencesParams{
HTTPClient: client,
}
}
/*PostSilencesParams contains all the parameters to send to the API endpoint
for the post silences operation typically these are written to a http.Request
/*
PostSilencesParams contains all the parameters to send to the API endpoint
for the post silences operation.
Typically these are written to a http.Request.
*/
type PostSilencesParams struct {
/*Silence
The silence to create
/* Silence.
The silence to create
*/
Silence *models.PostableSilence
@@ -88,6 +88,21 @@ type PostSilencesParams struct {
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the post silences params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *PostSilencesParams) WithDefaults() *PostSilencesParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the post silences params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *PostSilencesParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the post silences params
func (o *PostSilencesParams) WithTimeout(timeout time.Duration) *PostSilencesParams {
o.SetTimeout(timeout)
@@ -139,7 +154,6 @@ func (o *PostSilencesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.
return err
}
var res []error
if o.Silence != nil {
if err := r.SetBodyParam(o.Silence); err != nil {
return err

View File

@@ -20,13 +20,13 @@ package silence
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
strfmt "github.com/go-openapi/strfmt"
)
// PostSilencesReader is a Reader for the PostSilences structure.
@@ -55,9 +55,8 @@ func (o *PostSilencesReader) ReadResponse(response runtime.ClientResponse, consu
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("unknown error", response, response.Code())
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
@@ -66,7 +65,8 @@ func NewPostSilencesOK() *PostSilencesOK {
return &PostSilencesOK{}
}
/*PostSilencesOK handles this case with default header values.
/*
PostSilencesOK describes a response with status code 200, with default header values.
Create / update silence response
*/
@@ -74,10 +74,39 @@ type PostSilencesOK struct {
Payload *PostSilencesOKBody
}
// IsSuccess returns true when this post silences o k response has a 2xx status code
func (o *PostSilencesOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this post silences o k response has a 3xx status code
func (o *PostSilencesOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this post silences o k response has a 4xx status code
func (o *PostSilencesOK) IsClientError() bool {
return false
}
// IsServerError returns true when this post silences o k response has a 5xx status code
func (o *PostSilencesOK) IsServerError() bool {
return false
}
// IsCode returns true when this post silences o k response a status code equal to that given
func (o *PostSilencesOK) IsCode(code int) bool {
return code == 200
}
func (o *PostSilencesOK) Error() string {
return fmt.Sprintf("[POST /silences][%d] postSilencesOK %+v", 200, o.Payload)
}
func (o *PostSilencesOK) String() string {
return fmt.Sprintf("[POST /silences][%d] postSilencesOK %+v", 200, o.Payload)
}
func (o *PostSilencesOK) GetPayload() *PostSilencesOKBody {
return o.Payload
}
@@ -99,7 +128,8 @@ func NewPostSilencesBadRequest() *PostSilencesBadRequest {
return &PostSilencesBadRequest{}
}
/*PostSilencesBadRequest handles this case with default header values.
/*
PostSilencesBadRequest describes a response with status code 400, with default header values.
Bad request
*/
@@ -107,10 +137,39 @@ type PostSilencesBadRequest struct {
Payload string
}
// IsSuccess returns true when this post silences bad request response has a 2xx status code
func (o *PostSilencesBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this post silences bad request response has a 3xx status code
func (o *PostSilencesBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this post silences bad request response has a 4xx status code
func (o *PostSilencesBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this post silences bad request response has a 5xx status code
func (o *PostSilencesBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this post silences bad request response a status code equal to that given
func (o *PostSilencesBadRequest) IsCode(code int) bool {
return code == 400
}
func (o *PostSilencesBadRequest) Error() string {
return fmt.Sprintf("[POST /silences][%d] postSilencesBadRequest %+v", 400, o.Payload)
}
func (o *PostSilencesBadRequest) String() string {
return fmt.Sprintf("[POST /silences][%d] postSilencesBadRequest %+v", 400, o.Payload)
}
func (o *PostSilencesBadRequest) GetPayload() string {
return o.Payload
}
@@ -130,7 +189,8 @@ func NewPostSilencesNotFound() *PostSilencesNotFound {
return &PostSilencesNotFound{}
}
/*PostSilencesNotFound handles this case with default header values.
/*
PostSilencesNotFound describes a response with status code 404, with default header values.
A silence with the specified ID was not found
*/
@@ -138,10 +198,39 @@ type PostSilencesNotFound struct {
Payload string
}
// IsSuccess returns true when this post silences not found response has a 2xx status code
func (o *PostSilencesNotFound) IsSuccess() bool {
return false
}
// IsRedirect returns true when this post silences not found response has a 3xx status code
func (o *PostSilencesNotFound) IsRedirect() bool {
return false
}
// IsClientError returns true when this post silences not found response has a 4xx status code
func (o *PostSilencesNotFound) IsClientError() bool {
return true
}
// IsServerError returns true when this post silences not found response has a 5xx status code
func (o *PostSilencesNotFound) IsServerError() bool {
return false
}
// IsCode returns true when this post silences not found response a status code equal to that given
func (o *PostSilencesNotFound) IsCode(code int) bool {
return code == 404
}
func (o *PostSilencesNotFound) Error() string {
return fmt.Sprintf("[POST /silences][%d] postSilencesNotFound %+v", 404, o.Payload)
}
func (o *PostSilencesNotFound) String() string {
return fmt.Sprintf("[POST /silences][%d] postSilencesNotFound %+v", 404, o.Payload)
}
func (o *PostSilencesNotFound) GetPayload() string {
return o.Payload
}
@@ -156,7 +245,8 @@ func (o *PostSilencesNotFound) readResponse(response runtime.ClientResponse, con
return nil
}
/*PostSilencesOKBody post silences o k body
/*
PostSilencesOKBody post silences o k body
swagger:model PostSilencesOKBody
*/
type PostSilencesOKBody struct {
@@ -170,6 +260,11 @@ func (o *PostSilencesOKBody) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this post silences o k body based on context it is used
func (o *PostSilencesOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *PostSilencesOKBody) MarshalBinary() ([]byte, error) {
if o == nil {

View File

@@ -23,12 +23,11 @@ import (
"fmt"
"github.com/go-openapi/runtime"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/strfmt"
)
// New creates a new silence API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
@@ -40,16 +39,31 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
DeleteSilence(params *DeleteSilenceParams, opts ...ClientOption) (*DeleteSilenceOK, error)
GetSilence(params *GetSilenceParams, opts ...ClientOption) (*GetSilenceOK, error)
GetSilences(params *GetSilencesParams, opts ...ClientOption) (*GetSilencesOK, error)
PostSilences(params *PostSilencesParams, opts ...ClientOption) (*PostSilencesOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
DeleteSilence Delete a silence by its ID
*/
func (a *Client) DeleteSilence(params *DeleteSilenceParams) (*DeleteSilenceOK, error) {
func (a *Client) DeleteSilence(params *DeleteSilenceParams, opts ...ClientOption) (*DeleteSilenceOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewDeleteSilenceParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "deleteSilence",
Method: "DELETE",
PathPattern: "/silence/{silenceID}",
@@ -60,7 +74,12 @@ func (a *Client) DeleteSilence(params *DeleteSilenceParams) (*DeleteSilenceOK, e
Reader: &DeleteSilenceReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
@@ -77,13 +96,12 @@ func (a *Client) DeleteSilence(params *DeleteSilenceParams) (*DeleteSilenceOK, e
/*
GetSilence Get a silence by its ID
*/
func (a *Client) GetSilence(params *GetSilenceParams) (*GetSilenceOK, error) {
func (a *Client) GetSilence(params *GetSilenceParams, opts ...ClientOption) (*GetSilenceOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetSilenceParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "getSilence",
Method: "GET",
PathPattern: "/silence/{silenceID}",
@@ -94,7 +112,12 @@ func (a *Client) GetSilence(params *GetSilenceParams) (*GetSilenceOK, error) {
Reader: &GetSilenceReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
@@ -111,13 +134,12 @@ func (a *Client) GetSilence(params *GetSilenceParams) (*GetSilenceOK, error) {
/*
GetSilences Get a list of silences
*/
func (a *Client) GetSilences(params *GetSilencesParams) (*GetSilencesOK, error) {
func (a *Client) GetSilences(params *GetSilencesParams, opts ...ClientOption) (*GetSilencesOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetSilencesParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "getSilences",
Method: "GET",
PathPattern: "/silences",
@@ -128,7 +150,12 @@ func (a *Client) GetSilences(params *GetSilencesParams) (*GetSilencesOK, error)
Reader: &GetSilencesReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
@@ -145,13 +172,12 @@ func (a *Client) GetSilences(params *GetSilencesParams) (*GetSilencesOK, error)
/*
PostSilences Post a new silence or update an existing one
*/
func (a *Client) PostSilences(params *PostSilencesParams) (*PostSilencesOK, error) {
func (a *Client) PostSilences(params *PostSilencesParams, opts ...ClientOption) (*PostSilencesOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewPostSilencesParams()
}
result, err := a.transport.Submit(&runtime.ClientOperation{
op := &runtime.ClientOperation{
ID: "postSilences",
Method: "POST",
PathPattern: "/silences",
@@ -162,7 +188,12 @@ func (a *Client) PostSilences(params *PostSilencesParams) (*PostSilencesOK, erro
Reader: &PostSilencesReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
})
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Alert alert
//
// swagger:model alert
type Alert struct {
@@ -59,7 +61,6 @@ func (m *Alert) Validate(formats strfmt.Registry) error {
}
func (m *Alert) validateGeneratorURL(formats strfmt.Registry) error {
if swag.IsZero(m.GeneratorURL) { // not required
return nil
}
@@ -73,9 +74,45 @@ func (m *Alert) validateGeneratorURL(formats strfmt.Registry) error {
func (m *Alert) validateLabels(formats strfmt.Registry) error {
if err := m.Labels.Validate(formats); err != nil {
if err := validate.Required("labels", "body", m.Labels); err != nil {
return err
}
if m.Labels != nil {
if err := m.Labels.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labels")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("labels")
}
return err
}
}
return nil
}
// ContextValidate validate this alert based on the context it is used
func (m *Alert) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateLabels(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Alert) contextValidateLabels(ctx context.Context, formats strfmt.Registry) error {
if err := m.Labels.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labels")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("labels")
}
return err
}

View File

@@ -20,16 +20,17 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// AlertGroup alert group
//
// swagger:model alertGroup
type AlertGroup struct {
@@ -83,6 +84,8 @@ func (m *AlertGroup) validateAlerts(formats strfmt.Registry) error {
if err := m.Alerts[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("alerts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("alerts" + "." + strconv.Itoa(i))
}
return err
}
@@ -95,13 +98,21 @@ func (m *AlertGroup) validateAlerts(formats strfmt.Registry) error {
func (m *AlertGroup) validateLabels(formats strfmt.Registry) error {
if err := m.Labels.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labels")
}
if err := validate.Required("labels", "body", m.Labels); err != nil {
return err
}
if m.Labels != nil {
if err := m.Labels.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labels")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("labels")
}
return err
}
}
return nil
}
@@ -115,6 +126,80 @@ func (m *AlertGroup) validateReceiver(formats strfmt.Registry) error {
if err := m.Receiver.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("receiver")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("receiver")
}
return err
}
}
return nil
}
// ContextValidate validate this alert group based on the context it is used
func (m *AlertGroup) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAlerts(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateLabels(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateReceiver(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *AlertGroup) contextValidateAlerts(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Alerts); i++ {
if m.Alerts[i] != nil {
if err := m.Alerts[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("alerts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("alerts" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *AlertGroup) contextValidateLabels(ctx context.Context, formats strfmt.Registry) error {
if err := m.Labels.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labels")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("labels")
}
return err
}
return nil
}
func (m *AlertGroup) contextValidateReceiver(ctx context.Context, formats strfmt.Registry) error {
if m.Receiver != nil {
if err := m.Receiver.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("receiver")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("receiver")
}
return err
}

View File

@@ -20,15 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// AlertGroups alert groups
//
// swagger:model alertGroups
type AlertGroups []*AlertGroup
@@ -45,6 +46,33 @@ func (m AlertGroups) Validate(formats strfmt.Registry) error {
if err := m[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validate this alert groups based on the context it is used
func (m AlertGroups) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
for i := 0; i < len(m); i++ {
if m[i] != nil {
if err := m[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}

View File

@@ -20,16 +20,17 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// AlertStatus alert status
//
// swagger:model alertStatus
type AlertStatus struct {
@@ -113,7 +114,7 @@ const (
// prop value enum
func (m *AlertStatus) validateStateEnum(path, location string, value string) error {
if err := validate.Enum(path, location, value, alertStatusTypeStatePropEnum); err != nil {
if err := validate.EnumCase(path, location, value, alertStatusTypeStatePropEnum, true); err != nil {
return err
}
return nil
@@ -133,6 +134,11 @@ func (m *AlertStatus) validateState(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this alert status based on context it is used
func (m *AlertStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *AlertStatus) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// AlertmanagerConfig alertmanager config
//
// swagger:model alertmanagerConfig
type AlertmanagerConfig struct {
@@ -59,6 +61,11 @@ func (m *AlertmanagerConfig) validateOriginal(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this alertmanager config based on context it is used
func (m *AlertmanagerConfig) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *AlertmanagerConfig) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// AlertmanagerStatus alertmanager status
//
// swagger:model alertmanagerStatus
type AlertmanagerStatus struct {
@@ -85,6 +87,8 @@ func (m *AlertmanagerStatus) validateCluster(formats strfmt.Registry) error {
if err := m.Cluster.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("cluster")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("cluster")
}
return err
}
@@ -103,6 +107,8 @@ func (m *AlertmanagerStatus) validateConfig(formats strfmt.Registry) error {
if err := m.Config.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("config")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("config")
}
return err
}
@@ -134,6 +140,78 @@ func (m *AlertmanagerStatus) validateVersionInfo(formats strfmt.Registry) error
if err := m.VersionInfo.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("versionInfo")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("versionInfo")
}
return err
}
}
return nil
}
// ContextValidate validate this alertmanager status based on the context it is used
func (m *AlertmanagerStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateCluster(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateConfig(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateVersionInfo(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *AlertmanagerStatus) contextValidateCluster(ctx context.Context, formats strfmt.Registry) error {
if m.Cluster != nil {
if err := m.Cluster.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("cluster")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("cluster")
}
return err
}
}
return nil
}
func (m *AlertmanagerStatus) contextValidateConfig(ctx context.Context, formats strfmt.Registry) error {
if m.Config != nil {
if err := m.Config.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("config")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("config")
}
return err
}
}
return nil
}
func (m *AlertmanagerStatus) contextValidateVersionInfo(ctx context.Context, formats strfmt.Registry) error {
if m.VersionInfo != nil {
if err := m.VersionInfo.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("versionInfo")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("versionInfo")
}
return err
}

View File

@@ -20,17 +20,18 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ClusterStatus cluster status
//
// swagger:model clusterStatus
type ClusterStatus struct {
@@ -65,7 +66,6 @@ func (m *ClusterStatus) Validate(formats strfmt.Registry) error {
}
func (m *ClusterStatus) validatePeers(formats strfmt.Registry) error {
if swag.IsZero(m.Peers) { // not required
return nil
}
@@ -79,6 +79,8 @@ func (m *ClusterStatus) validatePeers(formats strfmt.Registry) error {
if err := m.Peers[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("peers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("peers" + "." + strconv.Itoa(i))
}
return err
}
@@ -115,7 +117,7 @@ const (
// prop value enum
func (m *ClusterStatus) validateStatusEnum(path, location string, value string) error {
if err := validate.Enum(path, location, value, clusterStatusTypeStatusPropEnum); err != nil {
if err := validate.EnumCase(path, location, value, clusterStatusTypeStatusPropEnum, true); err != nil {
return err
}
return nil
@@ -135,6 +137,40 @@ func (m *ClusterStatus) validateStatus(formats strfmt.Registry) error {
return nil
}
// ContextValidate validate this cluster status based on the context it is used
func (m *ClusterStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidatePeers(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ClusterStatus) contextValidatePeers(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Peers); i++ {
if m.Peers[i] != nil {
if err := m.Peers[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("peers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("peers" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ClusterStatus) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,16 +20,17 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// GettableAlert gettable alert
//
// swagger:model gettableAlert
type GettableAlert struct {
@@ -158,7 +159,6 @@ func (m GettableAlert) MarshalJSON() ([]byte, error) {
return nil, err
}
_parts = append(_parts, aO1)
return swag.ConcatJSON(_parts...), nil
}
@@ -207,13 +207,21 @@ func (m *GettableAlert) Validate(formats strfmt.Registry) error {
func (m *GettableAlert) validateAnnotations(formats strfmt.Registry) error {
if err := m.Annotations.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations")
}
if err := validate.Required("annotations", "body", m.Annotations); err != nil {
return err
}
if m.Annotations != nil {
if err := m.Annotations.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("annotations")
}
return err
}
}
return nil
}
@@ -254,6 +262,8 @@ func (m *GettableAlert) validateReceivers(formats strfmt.Registry) error {
if err := m.Receivers[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("receivers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("receivers" + "." + strconv.Itoa(i))
}
return err
}
@@ -287,6 +297,8 @@ func (m *GettableAlert) validateStatus(formats strfmt.Registry) error {
if err := m.Status.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("status")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("status")
}
return err
}
@@ -308,6 +320,83 @@ func (m *GettableAlert) validateUpdatedAt(formats strfmt.Registry) error {
return nil
}
// ContextValidate validate this gettable alert based on the context it is used
func (m *GettableAlert) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAnnotations(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateReceivers(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateStatus(ctx, formats); err != nil {
res = append(res, err)
}
// validation for a type composition with Alert
if err := m.Alert.ContextValidate(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *GettableAlert) contextValidateAnnotations(ctx context.Context, formats strfmt.Registry) error {
if err := m.Annotations.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("annotations")
}
return err
}
return nil
}
func (m *GettableAlert) contextValidateReceivers(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Receivers); i++ {
if m.Receivers[i] != nil {
if err := m.Receivers[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("receivers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("receivers" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *GettableAlert) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error {
if m.Status != nil {
if err := m.Status.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("status")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("status")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *GettableAlert) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,15 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// GettableAlerts gettable alerts
//
// swagger:model gettableAlerts
type GettableAlerts []*GettableAlert
@@ -45,6 +46,33 @@ func (m GettableAlerts) Validate(formats strfmt.Registry) error {
if err := m[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validate this gettable alerts based on the context it is used
func (m GettableAlerts) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
for i := 0; i < len(m); i++ {
if m[i] != nil {
if err := m[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// GettableSilence gettable silence
//
// swagger:model gettableSilence
type GettableSilence struct {
@@ -106,7 +108,6 @@ func (m GettableSilence) MarshalJSON() ([]byte, error) {
return nil, err
}
_parts = append(_parts, aO1)
return swag.ConcatJSON(_parts...), nil
}
@@ -156,6 +157,8 @@ func (m *GettableSilence) validateStatus(formats strfmt.Registry) error {
if err := m.Status.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("status")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("status")
}
return err
}
@@ -177,6 +180,41 @@ func (m *GettableSilence) validateUpdatedAt(formats strfmt.Registry) error {
return nil
}
// ContextValidate validate this gettable silence based on the context it is used
func (m *GettableSilence) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateStatus(ctx, formats); err != nil {
res = append(res, err)
}
// validation for a type composition with Silence
if err := m.Silence.ContextValidate(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *GettableSilence) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error {
if m.Status != nil {
if err := m.Status.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("status")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("status")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *GettableSilence) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,15 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// GettableSilences gettable silences
//
// swagger:model gettableSilences
type GettableSilences []*GettableSilence
@@ -45,6 +46,33 @@ func (m GettableSilences) Validate(formats strfmt.Registry) error {
if err := m[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validate this gettable silences based on the context it is used
func (m GettableSilences) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
for i := 0; i < len(m); i++ {
if m[i] != nil {
if err := m[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}

View File

@@ -20,10 +20,13 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/strfmt"
)
// LabelSet label set
//
// swagger:model labelSet
type LabelSet map[string]string
@@ -31,3 +34,8 @@ type LabelSet map[string]string
func (m LabelSet) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this label set based on context it is used
func (m LabelSet) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

View File

@@ -20,17 +20,22 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Matcher matcher
//
// swagger:model matcher
type Matcher struct {
// is equal
IsEqual *bool `json:"isEqual,omitempty"`
// is regex
// Required: true
IsRegex *bool `json:"isRegex"`
@@ -93,6 +98,11 @@ func (m *Matcher) validateValue(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this matcher based on context it is used
func (m *Matcher) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Matcher) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,16 +20,17 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Matchers matchers
//
// swagger:model matchers
type Matchers []*Matcher
@@ -52,6 +53,33 @@ func (m Matchers) Validate(formats strfmt.Registry) error {
if err := m[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validate this matchers based on the context it is used
func (m Matchers) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
for i := 0; i < len(m); i++ {
if m[i] != nil {
if err := m[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// PeerStatus peer status
//
// swagger:model peerStatus
type PeerStatus struct {
@@ -76,6 +78,11 @@ func (m *PeerStatus) validateName(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this peer status based on context it is used
func (m *PeerStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *PeerStatus) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// PostableAlert postable alert
//
// swagger:model postableAlert
type PostableAlert struct {
@@ -104,7 +106,6 @@ func (m PostableAlert) MarshalJSON() ([]byte, error) {
return nil, err
}
_parts = append(_parts, aO1)
return swag.ConcatJSON(_parts...), nil
}
@@ -141,11 +142,15 @@ func (m *PostableAlert) validateAnnotations(formats strfmt.Registry) error {
return nil
}
if err := m.Annotations.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations")
if m.Annotations != nil {
if err := m.Annotations.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("annotations")
}
return err
}
return err
}
return nil
@@ -177,6 +182,39 @@ func (m *PostableAlert) validateStartsAt(formats strfmt.Registry) error {
return nil
}
// ContextValidate validate this postable alert based on the context it is used
func (m *PostableAlert) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAnnotations(ctx, formats); err != nil {
res = append(res, err)
}
// validation for a type composition with Alert
if err := m.Alert.ContextValidate(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *PostableAlert) contextValidateAnnotations(ctx context.Context, formats strfmt.Registry) error {
if err := m.Annotations.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("annotations")
}
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *PostableAlert) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,15 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// PostableAlerts postable alerts
//
// swagger:model postableAlerts
type PostableAlerts []*PostableAlert
@@ -45,6 +46,33 @@ func (m PostableAlerts) Validate(formats strfmt.Registry) error {
if err := m[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validate this postable alerts based on the context it is used
func (m PostableAlerts) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
for i := 0; i < len(m); i++ {
if m[i] != nil {
if err := m[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName(strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName(strconv.Itoa(i))
}
return err
}

View File

@@ -20,13 +20,15 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// PostableSilence postable silence
//
// swagger:model postableSilence
type PostableSilence struct {
@@ -79,7 +81,6 @@ func (m PostableSilence) MarshalJSON() ([]byte, error) {
return nil, err
}
_parts = append(_parts, aO1)
return swag.ConcatJSON(_parts...), nil
}
@@ -98,6 +99,21 @@ func (m *PostableSilence) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validate this postable silence based on the context it is used
func (m *PostableSilence) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
// validation for a type composition with Silence
if err := m.Silence.ContextValidate(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// MarshalBinary interface implementation
func (m *PostableSilence) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Receiver receiver
//
// swagger:model receiver
type Receiver struct {
@@ -59,6 +61,11 @@ func (m *Receiver) validateName(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this receiver based on context it is used
func (m *Receiver) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Receiver) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Silence silence
//
// swagger:model silence
type Silence struct {
@@ -124,6 +126,8 @@ func (m *Silence) validateMatchers(formats strfmt.Registry) error {
if err := m.Matchers.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("matchers")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("matchers")
}
return err
}
@@ -144,6 +148,34 @@ func (m *Silence) validateStartsAt(formats strfmt.Registry) error {
return nil
}
// ContextValidate validate this silence based on the context it is used
func (m *Silence) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateMatchers(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Silence) contextValidateMatchers(ctx context.Context, formats strfmt.Registry) error {
if err := m.Matchers.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("matchers")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("matchers")
}
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *Silence) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,16 +20,17 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"encoding/json"
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// SilenceStatus silence status
//
// swagger:model silenceStatus
type SilenceStatus struct {
@@ -79,7 +80,7 @@ const (
// prop value enum
func (m *SilenceStatus) validateStateEnum(path, location string, value string) error {
if err := validate.Enum(path, location, value, silenceStatusTypeStatePropEnum); err != nil {
if err := validate.EnumCase(path, location, value, silenceStatusTypeStatePropEnum, true); err != nil {
return err
}
return nil
@@ -99,6 +100,11 @@ func (m *SilenceStatus) validateState(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this silence status based on context it is used
func (m *SilenceStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SilenceStatus) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -20,14 +20,16 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
strfmt "github.com/go-openapi/strfmt"
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// VersionInfo version info
//
// swagger:model versionInfo
type VersionInfo struct {
@@ -144,6 +146,11 @@ func (m *VersionInfo) validateVersion(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this version info based on context it is used
func (m *VersionInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *VersionInfo) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -14,8 +14,13 @@
package labels
import (
"bytes"
"encoding/json"
"fmt"
"regexp"
"strings"
"github.com/prometheus/common/model"
)
// MatchType is an enum for label matching types.
@@ -69,7 +74,7 @@ func NewMatcher(t MatchType, n, v string) (*Matcher, error) {
}
func (m *Matcher) String() string {
return fmt.Sprintf("%s%s%q", m.Name, m.Type, m.Value)
return fmt.Sprintf(`%s%s"%s"`, m.Name, m.Type, openMetricsEscape(m.Value))
}
// Matches returns whether the matcher matches the given string value.
@@ -86,3 +91,111 @@ func (m *Matcher) Matches(s string) bool {
}
panic("labels.Matcher.Matches: invalid match type")
}
type apiV1Matcher struct {
Name string `json:"name"`
Value string `json:"value"`
IsRegex bool `json:"isRegex"`
IsEqual bool `json:"isEqual"`
}
// MarshalJSON retains backwards compatibility with types.Matcher for the v1 API.
func (m Matcher) MarshalJSON() ([]byte, error) {
return json.Marshal(apiV1Matcher{
Name: m.Name,
Value: m.Value,
IsRegex: m.Type == MatchRegexp || m.Type == MatchNotRegexp,
IsEqual: m.Type == MatchRegexp || m.Type == MatchEqual,
})
}
func (m *Matcher) UnmarshalJSON(data []byte) error {
v1m := apiV1Matcher{
IsEqual: true,
}
if err := json.Unmarshal(data, &v1m); err != nil {
return err
}
var t MatchType
switch {
case v1m.IsEqual && !v1m.IsRegex:
t = MatchEqual
case !v1m.IsEqual && !v1m.IsRegex:
t = MatchNotEqual
case v1m.IsEqual && v1m.IsRegex:
t = MatchRegexp
case !v1m.IsEqual && v1m.IsRegex:
t = MatchNotRegexp
}
matcher, err := NewMatcher(t, v1m.Name, v1m.Value)
if err != nil {
return err
}
*m = *matcher
return nil
}
// openMetricsEscape is similar to the usual string escaping, but more
// restricted. It merely replaces a new-line character with '\n', a double-quote
// character with '\"', and a backslash with '\\', which is the escaping used by
// OpenMetrics.
func openMetricsEscape(s string) string {
r := strings.NewReplacer(
`\`, `\\`,
"\n", `\n`,
`"`, `\"`,
)
return r.Replace(s)
}
// Matchers is a slice of Matchers that is sortable, implements Stringer, and
// provides a Matches method to match a LabelSet against all Matchers in the
// slice. Note that some users of Matchers might require it to be sorted.
type Matchers []*Matcher
func (ms Matchers) Len() int { return len(ms) }
func (ms Matchers) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] }
func (ms Matchers) Less(i, j int) bool {
if ms[i].Name > ms[j].Name {
return false
}
if ms[i].Name < ms[j].Name {
return true
}
if ms[i].Value > ms[j].Value {
return false
}
if ms[i].Value < ms[j].Value {
return true
}
return ms[i].Type < ms[j].Type
}
// Matches checks whether all matchers are fulfilled against the given label set.
func (ms Matchers) Matches(lset model.LabelSet) bool {
for _, m := range ms {
if !m.Matches(string(lset[model.LabelName(m.Name)])) {
return false
}
}
return true
}
func (ms Matchers) String() string {
var buf bytes.Buffer
buf.WriteByte('{')
for i, m := range ms {
if i > 0 {
buf.WriteByte(',')
}
buf.WriteString(m.String())
}
buf.WriteByte('}')
return buf.String()
}

View File

@@ -16,12 +16,15 @@ package labels
import (
"regexp"
"strings"
"unicode/utf8"
"github.com/pkg/errors"
)
var (
re = regexp.MustCompile(`(?:\s?)(\w+)(=|=~|!=|!~)(?:\"([^"=~!]+)\"|([^"=~!]+)|\"\")`)
// '=~' has to come before '=' because otherwise only the '='
// will be consumed, and the '~' will be part of the 3rd token.
re = regexp.MustCompile(`^\s*([a-zA-Z_:][a-zA-Z0-9_:]*)\s*(=~|=|!=|!~)\s*((?s).*?)\s*$`)
typeMap = map[string]MatchType{
"=": MatchEqual,
"!=": MatchNotEqual,
@@ -30,27 +33,60 @@ var (
}
)
// ParseMatchers parses a comma-separated list of Matchers. A leading '{' and/or
// a trailing '}' is optional and will be trimmed before further
// parsing. Individual Matchers are separated by commas outside of quoted parts
// of the input string. Those commas may be surrounded by whitespace. Parts of the
// string inside unescaped double quotes ('"…"') are considered quoted (and
// commas don't act as separators there). If double quotes are escaped with a
// single backslash ('\"'), they are ignored for the purpose of identifying
// quoted parts of the input string. If the input string, after trimming the
// optional trailing '}', ends with a comma, followed by optional whitespace,
// this comma and whitespace will be trimmed.
//
// Examples for valid input strings:
//
// {foo = "bar", dings != "bums", }
// foo=bar,dings!=bums
// foo=bar, dings!=bums
// {quote="She said: \"Hi, ladies! That's gender-neutral…\""}
// statuscode=~"5.."
//
// See ParseMatcher for details on how an individual Matcher is parsed.
func ParseMatchers(s string) ([]*Matcher, error) {
matchers := []*Matcher{}
s = strings.TrimPrefix(s, "{")
s = strings.TrimSuffix(s, "}")
var insideQuotes bool
var token string
var tokens []string
var (
insideQuotes bool
escaped bool
token strings.Builder
tokens []string
)
for _, r := range s {
if !insideQuotes && r == ',' {
tokens = append(tokens, token)
token = ""
continue
}
token += string(r)
if r == '"' {
insideQuotes = !insideQuotes
switch r {
case ',':
if !insideQuotes {
tokens = append(tokens, token.String())
token.Reset()
continue
}
case '"':
if !escaped {
insideQuotes = !insideQuotes
} else {
escaped = false
}
case '\\':
escaped = !escaped
default:
escaped = false
}
token.WriteRune(r)
}
if token != "" {
tokens = append(tokens, token)
if s := strings.TrimSpace(token.String()); s != "" {
tokens = append(tokens, s)
}
for _, token := range tokens {
m, err := ParseMatcher(token)
@@ -63,32 +99,81 @@ func ParseMatchers(s string) ([]*Matcher, error) {
return matchers, nil
}
func ParseMatcher(s string) (*Matcher, error) {
var (
name, value string
matchType MatchType
)
// ParseMatcher parses a matcher with a syntax inspired by PromQL and
// OpenMetrics. This syntax is convenient to describe filters and selectors in
// UIs and config files. To support the interactive nature of the use cases, the
// parser is in various aspects fairly tolerant.
//
// The syntax of a matcher consists of three tokens: (1) A valid Prometheus
// label name. (2) One of '=', '!=', '=~', or '!~', with the same meaning as
// known from PromQL selectors. (3) A UTF-8 string, which may be enclosed in
// double quotes. Before or after each token, there may be any amount of
// whitespace, which will be discarded. The 3rd token may be the empty
// string. Within the 3rd token, OpenMetrics escaping rules apply: '\"' for a
// double-quote, '\n' for a line feed, '\\' for a literal backslash. Unescaped
// '"' must not occur inside the 3rd token (only as the 1st or last
// character). However, literal line feed characters are tolerated, as are
// single '\' characters not followed by '\', 'n', or '"'. They act as a literal
// backslash in that case.
func ParseMatcher(s string) (_ *Matcher, err error) {
ms := re.FindStringSubmatch(s)
if len(ms) < 4 {
if len(ms) == 0 {
return nil, errors.Errorf("bad matcher format: %s", s)
}
name = ms[1]
if name == "" {
return nil, errors.New("failed to parse label name")
var (
rawValue = ms[3]
value strings.Builder
escaped bool
expectTrailingQuote bool
)
if strings.HasPrefix(rawValue, "\"") {
rawValue = strings.TrimPrefix(rawValue, "\"")
expectTrailingQuote = true
}
matchType, found := typeMap[ms[2]]
if !found {
return nil, errors.New("failed to find match operator")
if !utf8.ValidString(rawValue) {
return nil, errors.Errorf("matcher value not valid UTF-8: %s", ms[3])
}
if ms[3] != "" {
value = ms[3]
} else {
value = ms[4]
// Unescape the rawValue:
for i, r := range rawValue {
if escaped {
escaped = false
switch r {
case 'n':
value.WriteByte('\n')
case '"', '\\':
value.WriteRune(r)
default:
// This was a spurious escape, so treat the '\' as literal.
value.WriteByte('\\')
value.WriteRune(r)
}
continue
}
switch r {
case '\\':
if i < len(rawValue)-1 {
escaped = true
continue
}
// '\' encountered as last byte. Treat it as literal.
value.WriteByte('\\')
case '"':
if !expectTrailingQuote || i < len(rawValue)-1 {
return nil, errors.Errorf("matcher value contains unescaped double quote: %s", ms[3])
}
expectTrailingQuote = false
default:
value.WriteRune(r)
}
}
return NewMatcher(matchType, name, value)
if expectTrailingQuote {
return nil, errors.Errorf("matcher value contains unescaped double quote: %s", ms[3])
}
return NewMatcher(typeMap[ms[2]], ms[1], value.String())
}

View File

@@ -18,6 +18,7 @@ package config
import (
"encoding/json"
"net/http"
"path/filepath"
)
@@ -34,7 +35,7 @@ func (s Secret) MarshalYAML() (interface{}, error) {
return nil, nil
}
//UnmarshalYAML implements the yaml.Unmarshaler interface for Secrets.
// UnmarshalYAML implements the yaml.Unmarshaler interface for Secrets.
func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain Secret
return unmarshal((*plain)(s))
@@ -48,6 +49,29 @@ func (s Secret) MarshalJSON() ([]byte, error) {
return json.Marshal(secretToken)
}
type Header map[string][]Secret
func (h *Header) HTTPHeader() http.Header {
if h == nil || *h == nil {
return nil
}
header := make(http.Header)
for name, values := range *h {
var s []string
if values != nil {
s = make([]string, 0, len(values))
for _, value := range values {
s = append(s, string(value))
}
}
header[name] = s
}
return header
}
// DirectorySetter is a config type that contains file paths that may
// be relative to the file containing the config.
type DirectorySetter interface {

View File

@@ -21,10 +21,11 @@ import (
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
@@ -80,7 +81,7 @@ func (tv *TLSVersion) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
func (tv *TLSVersion) MarshalYAML() (interface{}, error) {
if tv != nil || *tv == 0 {
if tv == nil || *tv == 0 {
return []byte("null"), nil
}
for s, v := range TLSVersions {
@@ -106,7 +107,7 @@ func (tv *TLSVersion) UnmarshalJSON(data []byte) error {
// MarshalJSON implements the json.Marshaler interface for TLSVersion.
func (tv *TLSVersion) MarshalJSON() ([]byte, error) {
if tv != nil || *tv == 0 {
if tv == nil || *tv == 0 {
return []byte("null"), nil
}
for s, v := range TLSVersions {
@@ -117,6 +118,19 @@ func (tv *TLSVersion) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("unknown TLS version: %d", tv)
}
// String implements the fmt.Stringer interface for TLSVersion.
func (tv *TLSVersion) String() string {
if tv == nil || *tv == 0 {
return ""
}
for s, v := range TLSVersions {
if *tv == v {
return s
}
}
return fmt.Sprintf("%d", tv)
}
// BasicAuth contains basic HTTP authentication credentials.
type BasicAuth struct {
Username string `yaml:"username" json:"username"`
@@ -235,6 +249,30 @@ func (a *OAuth2) SetDirectory(dir string) {
a.TLSConfig.SetDirectory(dir)
}
// LoadHTTPConfig parses the YAML input s into a HTTPClientConfig.
func LoadHTTPConfig(s string) (*HTTPClientConfig, error) {
cfg := &HTTPClientConfig{}
err := yaml.UnmarshalStrict([]byte(s), cfg)
if err != nil {
return nil, err
}
return cfg, nil
}
// LoadHTTPConfigFile parses the given YAML file into a HTTPClientConfig.
func LoadHTTPConfigFile(filename string) (*HTTPClientConfig, []byte, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, nil, err
}
cfg, err := LoadHTTPConfig(string(content))
if err != nil {
return nil, nil, err
}
cfg.SetDirectory(filepath.Dir(filepath.Dir(filename)))
return cfg, content, nil
}
// HTTPClientConfig configures an HTTP client.
type HTTPClientConfig struct {
// The HTTP basic authentication credentials for the targets.
@@ -251,6 +289,11 @@ type HTTPClientConfig struct {
BearerTokenFile string `yaml:"bearer_token_file,omitempty" json:"bearer_token_file,omitempty"`
// HTTP proxy server to use to connect to the targets.
ProxyURL URL `yaml:"proxy_url,omitempty" json:"proxy_url,omitempty"`
// ProxyConnectHeader optionally specifies headers to send to
// proxies during CONNECT requests. Assume that at least _some_ of
// these headers are going to contain secrets and use Secret as the
// value type instead of string.
ProxyConnectHeader Header `yaml:"proxy_connect_header,omitempty" json:"proxy_connect_header,omitempty"`
// TLSConfig to use to connect to the targets.
TLSConfig TLSConfig `yaml:"tls_config,omitempty" json:"tls_config,omitempty"`
// FollowRedirects specifies whether the client should follow HTTP 3xx redirects.
@@ -276,7 +319,8 @@ func (c *HTTPClientConfig) SetDirectory(dir string) {
}
// Validate validates the HTTPClientConfig to check only one of BearerToken,
// BasicAuth and BearerTokenFile is configured.
// BasicAuth and BearerTokenFile is configured. It also validates that ProxyURL
// is set if ProxyConnectHeader is set.
func (c *HTTPClientConfig) Validate() error {
// Backwards compatibility with the bearer_token field.
if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 {
@@ -334,6 +378,9 @@ func (c *HTTPClientConfig) Validate() error {
return fmt.Errorf("at most one of oauth2 client_secret & client_secret_file must be configured")
}
}
if len(c.ProxyConnectHeader) > 0 && (c.ProxyURL.URL == nil || c.ProxyURL.String() == "") {
return fmt.Errorf("if proxy_connect_header is configured proxy_url must also be configured")
}
return nil
}
@@ -462,6 +509,7 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT
// It is applied on request. So we leave out any timings here.
var rt http.RoundTripper = &http.Transport{
Proxy: http.ProxyURL(cfg.ProxyURL.URL),
ProxyConnectHeader: cfg.ProxyConnectHeader.HTTPHeader(),
MaxIdleConns: 20000,
MaxIdleConnsPerHost: 1000, // see https://github.com/golang/go/issues/13801
DisableKeepAlives: !opts.keepAlivesEnabled,
@@ -527,7 +575,7 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT
return newRT(tlsConfig)
}
return NewTLSRoundTripper(tlsConfig, cfg.TLSConfig.CAFile, newRT)
return NewTLSRoundTripper(tlsConfig, cfg.TLSConfig.CAFile, cfg.TLSConfig.CertFile, cfg.TLSConfig.KeyFile, newRT)
}
type authorizationCredentialsRoundTripper struct {
@@ -571,7 +619,7 @@ func NewAuthorizationCredentialsFileRoundTripper(authType, authCredentialsFile s
func (rt *authorizationCredentialsFileRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get("Authorization")) == 0 {
b, err := ioutil.ReadFile(rt.authCredentialsFile)
b, err := os.ReadFile(rt.authCredentialsFile)
if err != nil {
return nil, fmt.Errorf("unable to read authorization credentials file %s: %s", rt.authCredentialsFile, err)
}
@@ -609,7 +657,7 @@ func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
}
req = cloneRequest(req)
if rt.passwordFile != "" {
bs, err := ioutil.ReadFile(rt.passwordFile)
bs, err := os.ReadFile(rt.passwordFile)
if err != nil {
return nil, fmt.Errorf("unable to read basic auth password file %s: %s", rt.passwordFile, err)
}
@@ -651,7 +699,7 @@ func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
)
if rt.config.ClientSecretFile != "" {
data, err := ioutil.ReadFile(rt.config.ClientSecretFile)
data, err := os.ReadFile(rt.config.ClientSecretFile)
if err != nil {
return nil, fmt.Errorf("unable to read oauth2 client secret file %s: %s", rt.config.ClientSecretFile, err)
}
@@ -696,7 +744,7 @@ func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
if len(rt.config.TLSConfig.CAFile) == 0 {
t, _ = tlsTransport(tlsConfig)
} else {
t, err = NewTLSRoundTripper(tlsConfig, rt.config.TLSConfig.CAFile, tlsTransport)
t, err = NewTLSRoundTripper(tlsConfig, rt.config.TLSConfig.CAFile, rt.config.TLSConfig.CertFile, rt.config.TLSConfig.KeyFile, tlsTransport)
if err != nil {
return nil, err
}
@@ -766,6 +814,13 @@ func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: cfg.InsecureSkipVerify,
MinVersion: uint16(cfg.MinVersion),
MaxVersion: uint16(cfg.MaxVersion),
}
if cfg.MaxVersion != 0 && cfg.MinVersion != 0 {
if cfg.MaxVersion < cfg.MinVersion {
return nil, fmt.Errorf("tls_config.max_version must be greater than or equal to tls_config.min_version if both are specified")
}
}
// If a CA cert is provided then let's read it in so we can validate the
@@ -813,6 +868,8 @@ type TLSConfig struct {
InsecureSkipVerify bool `yaml:"insecure_skip_verify" json:"insecure_skip_verify"`
// Minimum TLS version.
MinVersion TLSVersion `yaml:"min_version,omitempty" json:"min_version,omitempty"`
// Maximum TLS version.
MaxVersion TLSVersion `yaml:"max_version,omitempty" json:"max_version,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
@@ -825,18 +882,45 @@ func (c *TLSConfig) SetDirectory(dir string) {
c.KeyFile = JoinDir(dir, c.KeyFile)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *TLSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain TLSConfig
return unmarshal((*plain)(c))
}
// readCertAndKey reads the cert and key files from the disk.
func readCertAndKey(certFile, keyFile string) ([]byte, []byte, error) {
certData, err := os.ReadFile(certFile)
if err != nil {
return nil, nil, err
}
keyData, err := os.ReadFile(keyFile)
if err != nil {
return nil, nil, err
}
return certData, keyData, nil
}
// getClientCertificate reads the pair of client cert and key from disk and returns a tls.Certificate.
func (c *TLSConfig) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
func (c *TLSConfig) getClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) {
certData, keyData, err := readCertAndKey(c.CertFile, c.KeyFile)
if err != nil {
return nil, fmt.Errorf("unable to read specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
}
cert, err := tls.X509KeyPair(certData, keyData)
if err != nil {
return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
}
return &cert, nil
}
// readCAFile reads the CA cert file from disk.
func readCAFile(f string) ([]byte, error) {
data, err := ioutil.ReadFile(f)
data, err := os.ReadFile(f)
if err != nil {
return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
}
@@ -856,23 +940,30 @@ func updateRootCA(cfg *tls.Config, b []byte) bool {
// tlsRoundTripper is a RoundTripper that updates automatically its TLS
// configuration whenever the content of the CA file changes.
type tlsRoundTripper struct {
caFile string
caFile string
certFile string
keyFile string
// newRT returns a new RoundTripper.
newRT func(*tls.Config) (http.RoundTripper, error)
mtx sync.RWMutex
rt http.RoundTripper
hashCAFile []byte
tlsConfig *tls.Config
mtx sync.RWMutex
rt http.RoundTripper
hashCAFile []byte
hashCertFile []byte
hashKeyFile []byte
tlsConfig *tls.Config
}
func NewTLSRoundTripper(
cfg *tls.Config,
caFile string,
caFile, certFile, keyFile string,
newRT func(*tls.Config) (http.RoundTripper, error),
) (http.RoundTripper, error) {
t := &tlsRoundTripper{
caFile: caFile,
certFile: certFile,
keyFile: keyFile,
newRT: newRT,
tlsConfig: cfg,
}
@@ -882,7 +973,7 @@ func NewTLSRoundTripper(
return nil, err
}
t.rt = rt
_, t.hashCAFile, err = t.getCAWithHash()
_, t.hashCAFile, t.hashCertFile, t.hashKeyFile, err = t.getTLSFilesWithHash()
if err != nil {
return nil, err
}
@@ -890,25 +981,36 @@ func NewTLSRoundTripper(
return t, nil
}
func (t *tlsRoundTripper) getCAWithHash() ([]byte, []byte, error) {
b, err := readCAFile(t.caFile)
func (t *tlsRoundTripper) getTLSFilesWithHash() ([]byte, []byte, []byte, []byte, error) {
b1, err := readCAFile(t.caFile)
if err != nil {
return nil, nil, err
return nil, nil, nil, nil, err
}
h := sha256.Sum256(b)
return b, h[:], nil
h1 := sha256.Sum256(b1)
var h2, h3 [32]byte
if t.certFile != "" {
b2, b3, err := readCertAndKey(t.certFile, t.keyFile)
if err != nil {
return nil, nil, nil, nil, err
}
h2, h3 = sha256.Sum256(b2), sha256.Sum256(b3)
}
return b1, h1[:], h2[:], h3[:], nil
}
// RoundTrip implements the http.RoundTrip interface.
func (t *tlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
b, h, err := t.getCAWithHash()
caData, caHash, certHash, keyHash, err := t.getTLSFilesWithHash()
if err != nil {
return nil, err
}
t.mtx.RLock()
equal := bytes.Equal(h[:], t.hashCAFile)
equal := bytes.Equal(caHash[:], t.hashCAFile) &&
bytes.Equal(certHash[:], t.hashCertFile) &&
bytes.Equal(keyHash[:], t.hashKeyFile)
rt := t.rt
t.mtx.RUnlock()
if equal {
@@ -917,8 +1019,10 @@ func (t *tlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
}
// Create a new RoundTripper.
// The cert and key files are read separately by the client
// using GetClientCertificate.
tlsConfig := t.tlsConfig.Clone()
if !updateRootCA(tlsConfig, b) {
if !updateRootCA(tlsConfig, caData) {
return nil, fmt.Errorf("unable to use specified CA cert %s", t.caFile)
}
rt, err = t.newRT(tlsConfig)
@@ -929,7 +1033,9 @@ func (t *tlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
t.mtx.Lock()
t.rt = rt
t.hashCAFile = h[:]
t.hashCAFile = caHash[:]
t.hashCertFile = certHash[:]
t.hashKeyFile = keyHash[:]
t.mtx.Unlock()
return rt.RoundTrip(req)

View File

@@ -21,8 +21,8 @@ import "bytes"
// Fuzz text metric parser with with github.com/dvyukov/go-fuzz:
//
// go-fuzz-build github.com/prometheus/common/expfmt
// go-fuzz -bin expfmt-fuzz.zip -workdir fuzz
// go-fuzz-build github.com/prometheus/common/expfmt
// go-fuzz -bin expfmt-fuzz.zip -workdir fuzz
//
// Further input samples should go in the folder fuzz/corpus.
func Fuzz(in []byte) int {

View File

@@ -46,20 +46,20 @@ import (
// missing features and peculiarities to avoid complications when switching from
// Prometheus to OpenMetrics or vice versa:
//
// - Counters are expected to have the `_total` suffix in their metric name. In
// the output, the suffix will be truncated from the `# TYPE` and `# HELP`
// line. A counter with a missing `_total` suffix is not an error. However,
// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
// output.
// - Counters are expected to have the `_total` suffix in their metric name. In
// the output, the suffix will be truncated from the `# TYPE` and `# HELP`
// line. A counter with a missing `_total` suffix is not an error. However,
// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
// output.
//
// - No support for the following (optional) features: `# UNIT` line, `_created`
// line, info type, stateset type, gaugehistogram type.
// - No support for the following (optional) features: `# UNIT` line, `_created`
// line, info type, stateset type, gaugehistogram type.
//
// - The size of exemplar labels is not checked (i.e. it's possible to create
// exemplars that are larger than allowed by the OpenMetrics specification).
// - The size of exemplar labels is not checked (i.e. it's possible to create
// exemplars that are larger than allowed by the OpenMetrics specification).
//
// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
// with a `NaN` value.)
// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
// with a `NaN` value.)
func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
name := in.GetName()
if name == "" {

View File

@@ -17,7 +17,6 @@ import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math"
"strconv"
"strings"
@@ -44,7 +43,7 @@ const (
var (
bufPool = sync.Pool{
New: func() interface{} {
return bufio.NewWriter(ioutil.Discard)
return bufio.NewWriter(io.Discard)
},
}
numBufPool = sync.Pool{

View File

@@ -11,18 +11,18 @@ 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 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.
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 the Open Knowledge Foundation Ltd. nor the
names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
Neither the name of the Open Knowledge Foundation Ltd. 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
@@ -35,8 +35,6 @@ 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.
*/
package goautoneg

201
vendor/github.com/prometheus/common/sigv4/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor 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, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

22
vendor/github.com/prometheus/common/sigv4/Makefile generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# Copyright 2018 The Prometheus Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
include ../Makefile.common
.PHONY: test
@echo ">> Running sigv4 tests"
test:: deps check_license unused common-test
ifeq (,$(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(7|8|9|10)\.'))
test:: lint
endif

137
vendor/github.com/prometheus/common/sigv4/sigv4.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sigv4
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/textproto"
"sync"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
signer "github.com/aws/aws-sdk-go/aws/signer/v4"
)
var sigv4HeaderDenylist = []string{
"uber-trace-id",
}
type sigV4RoundTripper struct {
region string
next http.RoundTripper
pool sync.Pool
signer *signer.Signer
}
// NewSigV4RoundTripper returns a new http.RoundTripper that will sign requests
// using Amazon's Signature Verification V4 signing procedure. The request will
// then be handed off to the next RoundTripper provided by next. If next is nil,
// http.DefaultTransport will be used.
//
// Credentials for signing are retrieved using the the default AWS credential
// chain. If credentials cannot be found, an error will be returned.
func NewSigV4RoundTripper(cfg *SigV4Config, next http.RoundTripper) (http.RoundTripper, error) {
if next == nil {
next = http.DefaultTransport
}
creds := credentials.NewStaticCredentials(cfg.AccessKey, string(cfg.SecretKey), "")
if cfg.AccessKey == "" && cfg.SecretKey == "" {
creds = nil
}
sess, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Region: aws.String(cfg.Region),
Credentials: creds,
},
Profile: cfg.Profile,
})
if err != nil {
return nil, fmt.Errorf("could not create new AWS session: %w", err)
}
if _, err := sess.Config.Credentials.Get(); err != nil {
return nil, fmt.Errorf("could not get SigV4 credentials: %w", err)
}
if aws.StringValue(sess.Config.Region) == "" {
return nil, fmt.Errorf("region not configured in sigv4 or in default credentials chain")
}
signerCreds := sess.Config.Credentials
if cfg.RoleARN != "" {
signerCreds = stscreds.NewCredentials(sess, cfg.RoleARN)
}
rt := &sigV4RoundTripper{
region: cfg.Region,
next: next,
signer: signer.NewSigner(signerCreds),
}
rt.pool.New = rt.newBuf
return rt, nil
}
func (rt *sigV4RoundTripper) newBuf() interface{} {
return bytes.NewBuffer(make([]byte, 0, 1024))
}
func (rt *sigV4RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// rt.signer.Sign needs a seekable body, so we replace the body with a
// buffered reader filled with the contents of original body.
buf := rt.pool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
rt.pool.Put(buf)
}()
if _, err := io.Copy(buf, req.Body); err != nil {
return nil, err
}
// Close the original body since we don't need it anymore.
_ = req.Body.Close()
// Ensure our seeker is back at the start of the buffer once we return.
var seeker io.ReadSeeker = bytes.NewReader(buf.Bytes())
defer func() {
_, _ = seeker.Seek(0, io.SeekStart)
}()
req.Body = ioutil.NopCloser(seeker)
// Clone the request and trim out headers that we don't want to sign.
signReq := req.Clone(req.Context())
for _, header := range sigv4HeaderDenylist {
signReq.Header.Del(header)
}
headers, err := rt.signer.Sign(signReq, seeker, "aps", rt.region, time.Now().UTC())
if err != nil {
return nil, fmt.Errorf("failed to sign request: %w", err)
}
// Copy over signed headers. Authorization header is not returned by
// rt.signer.Sign and needs to be copied separately.
for k, v := range headers {
req.Header[textproto.CanonicalMIMEHeaderKey(k)] = v
}
req.Header.Set("Authorization", signReq.Header.Get("Authorization"))
return rt.next.RoundTrip(req)
}

View File

@@ -0,0 +1,47 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sigv4
import (
"fmt"
"github.com/prometheus/common/config"
)
// SigV4Config is the configuration for signing remote write requests with
// AWS's SigV4 verification process. Empty values will be retrieved using the
// AWS default credentials chain.
type SigV4Config struct {
Region string `yaml:"region,omitempty"`
AccessKey string `yaml:"access_key,omitempty"`
SecretKey config.Secret `yaml:"secret_key,omitempty"`
Profile string `yaml:"profile,omitempty"`
RoleARN string `yaml:"role_arn,omitempty"`
}
func (c *SigV4Config) Validate() error {
if (c.AccessKey == "") != (c.SecretKey == "") {
return fmt.Errorf("must provide a AWS SigV4 Access key and Secret Key if credentials are specified in the SigV4 config")
}
return nil
}
func (c *SigV4Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain SigV4Config
*c = SigV4Config{}
if err := unmarshal((*plain)(c)); err != nil {
return err
}
return c.Validate()
}

100
vendor/github.com/prometheus/common/version/info.go generated vendored Normal file
View File

@@ -0,0 +1,100 @@
// Copyright 2016 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
import (
"bytes"
"fmt"
"runtime"
"strings"
"text/template"
"github.com/prometheus/client_golang/prometheus"
)
// Build information. Populated at build-time.
var (
Version string
Revision string
Branch string
BuildUser string
BuildDate string
GoVersion = runtime.Version()
GoOS = runtime.GOOS
GoArch = runtime.GOARCH
)
// NewCollector returns a collector that exports metrics about current version
// information.
func NewCollector(program string) prometheus.Collector {
return prometheus.NewGaugeFunc(
prometheus.GaugeOpts{
Namespace: program,
Name: "build_info",
Help: fmt.Sprintf(
"A metric with a constant '1' value labeled by version, revision, branch, goversion from which %s was built, and the goos and goarch for the build.",
program,
),
ConstLabels: prometheus.Labels{
"version": Version,
"revision": getRevision(),
"branch": Branch,
"goversion": GoVersion,
"goos": GoOS,
"goarch": GoArch,
},
},
func() float64 { return 1 },
)
}
// versionInfoTmpl contains the template used by Info.
var versionInfoTmpl = `
{{.program}}, version {{.version}} (branch: {{.branch}}, revision: {{.revision}})
build user: {{.buildUser}}
build date: {{.buildDate}}
go version: {{.goVersion}}
platform: {{.platform}}
`
// Print returns version information.
func Print(program string) string {
m := map[string]string{
"program": program,
"version": Version,
"revision": getRevision(),
"branch": Branch,
"buildUser": BuildUser,
"buildDate": BuildDate,
"goVersion": GoVersion,
"platform": GoOS + "/" + GoArch,
}
t := template.Must(template.New("version").Parse(versionInfoTmpl))
var buf bytes.Buffer
if err := t.ExecuteTemplate(&buf, "version", m); err != nil {
panic(err)
}
return strings.TrimSpace(buf.String())
}
// Info returns version, branch and revision information.
func Info() string {
return fmt.Sprintf("(version=%s, branch=%s, revision=%s)", Version, Branch, getRevision())
}
// BuildContext returns goVersion, platform, buildUser and buildDate information.
func BuildContext() string {
return fmt.Sprintf("(go=%s, platform=%s, user=%s, date=%s)", GoVersion, GoOS+"/"+GoArch, BuildUser, BuildDate)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2019 The Prometheus Authors
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -11,14 +11,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar
//go:build !go1.18
// +build !go1.18
import "github.com/prometheus/prometheus/pkg/labels"
package version
// Exemplar is additional information associated with a time series.
type Exemplar struct {
Labels labels.Labels
Value float64
HasTs bool
Ts int64
func getRevision() string {
return Revision
}

View File

@@ -0,0 +1,58 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build go1.18
// +build go1.18
package version
import "runtime/debug"
var computedRevision string
func getRevision() string {
if Revision != "" {
return Revision
}
return computedRevision
}
func init() {
computedRevision = computeRevision()
}
func computeRevision() string {
var (
rev = "unknown"
modified bool
)
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return rev
}
for _, v := range buildInfo.Settings {
if v.Key == "vcs.revision" {
rev = v.Value
}
if v.Key == "vcs.modified" {
if v.Value == "true" {
modified = true
}
}
}
if modified {
return rev + "-modified"
}
return rev
}

View File

@@ -91,8 +91,18 @@ https://github.com/dgryski/go-tsz
Copyright (c) 2015,2016 Damian Gryski <damian@gryski.com>
See https://github.com/dgryski/go-tsz/blob/master/LICENSE for license details.
The Go programming language
https://go.dev/
Copyright (c) 2009 The Go Authors
See https://go.dev/LICENSE for license details.
The Codicon icon font from Microsoft
https://github.com/microsoft/vscode-codicons
Copyright (c) Microsoft Corporation and other contributors
See https://github.com/microsoft/vscode-codicons/blob/main/LICENSE for license details.
We also use code from a large number of npm packages. For details, see:
- https://github.com/prometheus/prometheus/blob/master/web/ui/react-app/package.json
- https://github.com/prometheus/prometheus/blob/master/web/ui/react-app/package-lock.json
- https://github.com/prometheus/prometheus/blob/main/web/ui/react-app/package.json
- https://github.com/prometheus/prometheus/blob/main/web/ui/react-app/package-lock.json
- The individual package licenses as copied from the node_modules directory can be found in
the npm_licenses.tar.bz2 archive in release tarballs and Docker images.

View File

@@ -0,0 +1,938 @@
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/alecthomas/units"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/grafana/regexp"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/sigv4"
"gopkg.in/yaml.v2"
"github.com/prometheus/prometheus/discovery"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/relabel"
)
var (
patRulePath = regexp.MustCompile(`^[^*]*(\*[^/]*)?$`)
reservedHeaders = map[string]struct{}{
// NOTE: authorization is checked specially,
// see RemoteWriteConfig.UnmarshalYAML.
// "authorization": {},
"host": {},
"content-encoding": {},
"content-length": {},
"content-type": {},
"user-agent": {},
"connection": {},
"keep-alive": {},
"proxy-authenticate": {},
"proxy-authorization": {},
"www-authenticate": {},
"accept-encoding": {},
"x-prometheus-remote-write-version": {},
"x-prometheus-remote-read-version": {},
// Added by SigV4.
"x-amz-date": {},
"x-amz-security-token": {},
"x-amz-content-sha256": {},
}
)
// Load parses the YAML input s into a Config.
func Load(s string, expandExternalLabels bool, logger log.Logger) (*Config, error) {
cfg := &Config{}
// If the entire config body is empty the UnmarshalYAML method is
// never called. We thus have to set the DefaultConfig at the entry
// point as well.
*cfg = DefaultConfig
err := yaml.UnmarshalStrict([]byte(s), cfg)
if err != nil {
return nil, err
}
if !expandExternalLabels {
return cfg, nil
}
b := labels.ScratchBuilder{}
cfg.GlobalConfig.ExternalLabels.Range(func(v labels.Label) {
newV := os.Expand(v.Value, func(s string) string {
if s == "$" {
return "$"
}
if v := os.Getenv(s); v != "" {
return v
}
level.Warn(logger).Log("msg", "Empty environment variable", "name", s)
return ""
})
if newV != v.Value {
level.Debug(logger).Log("msg", "External label replaced", "label", v.Name, "input", v.Value, "output", newV)
}
b.Add(v.Name, newV)
})
cfg.GlobalConfig.ExternalLabels = b.Labels()
return cfg, nil
}
// LoadFile parses the given YAML file into a Config.
func LoadFile(filename string, agentMode, expandExternalLabels bool, logger log.Logger) (*Config, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
cfg, err := Load(string(content), expandExternalLabels, logger)
if err != nil {
return nil, fmt.Errorf("parsing YAML file %s: %w", filename, err)
}
if agentMode {
if len(cfg.AlertingConfig.AlertmanagerConfigs) > 0 || len(cfg.AlertingConfig.AlertRelabelConfigs) > 0 {
return nil, errors.New("field alerting is not allowed in agent mode")
}
if len(cfg.RuleFiles) > 0 {
return nil, errors.New("field rule_files is not allowed in agent mode")
}
if len(cfg.RemoteReadConfigs) > 0 {
return nil, errors.New("field remote_read is not allowed in agent mode")
}
}
cfg.SetDirectory(filepath.Dir(filename))
return cfg, nil
}
// The defaults applied before parsing the respective config sections.
var (
// DefaultConfig is the default top-level configuration.
DefaultConfig = Config{
GlobalConfig: DefaultGlobalConfig,
}
// DefaultGlobalConfig is the default global configuration.
DefaultGlobalConfig = GlobalConfig{
ScrapeInterval: model.Duration(1 * time.Minute),
ScrapeTimeout: model.Duration(10 * time.Second),
EvaluationInterval: model.Duration(1 * time.Minute),
}
// DefaultScrapeConfig is the default scrape configuration.
DefaultScrapeConfig = ScrapeConfig{
// ScrapeTimeout and ScrapeInterval default to the
// configured globals.
MetricsPath: "/metrics",
Scheme: "http",
HonorLabels: false,
HonorTimestamps: true,
HTTPClientConfig: config.DefaultHTTPClientConfig,
}
// DefaultAlertmanagerConfig is the default alertmanager configuration.
DefaultAlertmanagerConfig = AlertmanagerConfig{
Scheme: "http",
Timeout: model.Duration(10 * time.Second),
APIVersion: AlertmanagerAPIVersionV2,
HTTPClientConfig: config.DefaultHTTPClientConfig,
}
// DefaultRemoteWriteConfig is the default remote write configuration.
DefaultRemoteWriteConfig = RemoteWriteConfig{
RemoteTimeout: model.Duration(30 * time.Second),
QueueConfig: DefaultQueueConfig,
MetadataConfig: DefaultMetadataConfig,
HTTPClientConfig: config.DefaultHTTPClientConfig,
}
// DefaultQueueConfig is the default remote queue configuration.
DefaultQueueConfig = QueueConfig{
// With a maximum of 200 shards, assuming an average of 100ms remote write
// time and 500 samples per batch, we will be able to push 1M samples/s.
MaxShards: 200,
MinShards: 1,
MaxSamplesPerSend: 500,
// Each shard will have a max of 2500 samples pending in its channel, plus the pending
// samples that have been enqueued. Theoretically we should only ever have about 3000 samples
// per shard pending. At 200 shards that's 600k.
Capacity: 2500,
BatchSendDeadline: model.Duration(5 * time.Second),
// Backoff times for retrying a batch of samples on recoverable errors.
MinBackoff: model.Duration(30 * time.Millisecond),
MaxBackoff: model.Duration(5 * time.Second),
}
// DefaultMetadataConfig is the default metadata configuration for a remote write endpoint.
DefaultMetadataConfig = MetadataConfig{
Send: true,
SendInterval: model.Duration(1 * time.Minute),
MaxSamplesPerSend: 500,
}
// DefaultRemoteReadConfig is the default remote read configuration.
DefaultRemoteReadConfig = RemoteReadConfig{
RemoteTimeout: model.Duration(1 * time.Minute),
HTTPClientConfig: config.DefaultHTTPClientConfig,
FilterExternalLabels: true,
}
// DefaultStorageConfig is the default TSDB/Exemplar storage configuration.
DefaultStorageConfig = StorageConfig{
ExemplarsConfig: &DefaultExemplarsConfig,
}
DefaultExemplarsConfig = ExemplarsConfig{
MaxExemplars: 100000,
}
)
// Config is the top-level configuration for Prometheus's config files.
type Config struct {
GlobalConfig GlobalConfig `yaml:"global"`
AlertingConfig AlertingConfig `yaml:"alerting,omitempty"`
RuleFiles []string `yaml:"rule_files,omitempty"`
ScrapeConfigs []*ScrapeConfig `yaml:"scrape_configs,omitempty"`
StorageConfig StorageConfig `yaml:"storage,omitempty"`
TracingConfig TracingConfig `yaml:"tracing,omitempty"`
RemoteWriteConfigs []*RemoteWriteConfig `yaml:"remote_write,omitempty"`
RemoteReadConfigs []*RemoteReadConfig `yaml:"remote_read,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (c *Config) SetDirectory(dir string) {
c.GlobalConfig.SetDirectory(dir)
c.AlertingConfig.SetDirectory(dir)
c.TracingConfig.SetDirectory(dir)
for i, file := range c.RuleFiles {
c.RuleFiles[i] = config.JoinDir(dir, file)
}
for _, c := range c.ScrapeConfigs {
c.SetDirectory(dir)
}
for _, c := range c.RemoteWriteConfigs {
c.SetDirectory(dir)
}
for _, c := range c.RemoteReadConfigs {
c.SetDirectory(dir)
}
}
func (c Config) String() string {
b, err := yaml.Marshal(c)
if err != nil {
return fmt.Sprintf("<error creating config string: %s>", err)
}
return string(b)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultConfig
// We want to set c to the defaults and then overwrite it with the input.
// To make unmarshal fill the plain data struct rather than calling UnmarshalYAML
// again, we have to hide it using a type indirection.
type plain Config
if err := unmarshal((*plain)(c)); err != nil {
return err
}
// If a global block was open but empty the default global config is overwritten.
// We have to restore it here.
if c.GlobalConfig.isZero() {
c.GlobalConfig = DefaultGlobalConfig
}
for _, rf := range c.RuleFiles {
if !patRulePath.MatchString(rf) {
return fmt.Errorf("invalid rule file path %q", rf)
}
}
// Do global overrides and validate unique names.
jobNames := map[string]struct{}{}
for _, scfg := range c.ScrapeConfigs {
if scfg == nil {
return errors.New("empty or null scrape config section")
}
// First set the correct scrape interval, then check that the timeout
// (inferred or explicit) is not greater than that.
if scfg.ScrapeInterval == 0 {
scfg.ScrapeInterval = c.GlobalConfig.ScrapeInterval
}
if scfg.ScrapeTimeout > scfg.ScrapeInterval {
return fmt.Errorf("scrape timeout greater than scrape interval for scrape config with job name %q", scfg.JobName)
}
if scfg.ScrapeTimeout == 0 {
if c.GlobalConfig.ScrapeTimeout > scfg.ScrapeInterval {
scfg.ScrapeTimeout = scfg.ScrapeInterval
} else {
scfg.ScrapeTimeout = c.GlobalConfig.ScrapeTimeout
}
}
if _, ok := jobNames[scfg.JobName]; ok {
return fmt.Errorf("found multiple scrape configs with job name %q", scfg.JobName)
}
jobNames[scfg.JobName] = struct{}{}
}
rwNames := map[string]struct{}{}
for _, rwcfg := range c.RemoteWriteConfigs {
if rwcfg == nil {
return errors.New("empty or null remote write config section")
}
// Skip empty names, we fill their name with their config hash in remote write code.
if _, ok := rwNames[rwcfg.Name]; ok && rwcfg.Name != "" {
return fmt.Errorf("found multiple remote write configs with job name %q", rwcfg.Name)
}
rwNames[rwcfg.Name] = struct{}{}
}
rrNames := map[string]struct{}{}
for _, rrcfg := range c.RemoteReadConfigs {
if rrcfg == nil {
return errors.New("empty or null remote read config section")
}
// Skip empty names, we fill their name with their config hash in remote read code.
if _, ok := rrNames[rrcfg.Name]; ok && rrcfg.Name != "" {
return fmt.Errorf("found multiple remote read configs with job name %q", rrcfg.Name)
}
rrNames[rrcfg.Name] = struct{}{}
}
return nil
}
// GlobalConfig configures values that are used across other configuration
// objects.
type GlobalConfig struct {
// How frequently to scrape targets by default.
ScrapeInterval model.Duration `yaml:"scrape_interval,omitempty"`
// The default timeout when scraping targets.
ScrapeTimeout model.Duration `yaml:"scrape_timeout,omitempty"`
// How frequently to evaluate rules by default.
EvaluationInterval model.Duration `yaml:"evaluation_interval,omitempty"`
// File to which PromQL queries are logged.
QueryLogFile string `yaml:"query_log_file,omitempty"`
// The labels to add to any timeseries that this Prometheus instance scrapes.
ExternalLabels labels.Labels `yaml:"external_labels,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (c *GlobalConfig) SetDirectory(dir string) {
c.QueryLogFile = config.JoinDir(dir, c.QueryLogFile)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Create a clean global config as the previous one was already populated
// by the default due to the YAML parser behavior for empty blocks.
gc := &GlobalConfig{}
type plain GlobalConfig
if err := unmarshal((*plain)(gc)); err != nil {
return err
}
if err := gc.ExternalLabels.Validate(func(l labels.Label) error {
if !model.LabelName(l.Name).IsValid() {
return fmt.Errorf("%q is not a valid label name", l.Name)
}
if !model.LabelValue(l.Value).IsValid() {
return fmt.Errorf("%q is not a valid label value", l.Value)
}
return nil
}); err != nil {
return err
}
// First set the correct scrape interval, then check that the timeout
// (inferred or explicit) is not greater than that.
if gc.ScrapeInterval == 0 {
gc.ScrapeInterval = DefaultGlobalConfig.ScrapeInterval
}
if gc.ScrapeTimeout > gc.ScrapeInterval {
return errors.New("global scrape timeout greater than scrape interval")
}
if gc.ScrapeTimeout == 0 {
if DefaultGlobalConfig.ScrapeTimeout > gc.ScrapeInterval {
gc.ScrapeTimeout = gc.ScrapeInterval
} else {
gc.ScrapeTimeout = DefaultGlobalConfig.ScrapeTimeout
}
}
if gc.EvaluationInterval == 0 {
gc.EvaluationInterval = DefaultGlobalConfig.EvaluationInterval
}
*c = *gc
return nil
}
// isZero returns true iff the global config is the zero value.
func (c *GlobalConfig) isZero() bool {
return c.ExternalLabels.IsEmpty() &&
c.ScrapeInterval == 0 &&
c.ScrapeTimeout == 0 &&
c.EvaluationInterval == 0 &&
c.QueryLogFile == ""
}
// ScrapeConfig configures a scraping unit for Prometheus.
type ScrapeConfig struct {
// The job name to which the job label is set by default.
JobName string `yaml:"job_name"`
// Indicator whether the scraped metrics should remain unmodified.
HonorLabels bool `yaml:"honor_labels,omitempty"`
// Indicator whether the scraped timestamps should be respected.
HonorTimestamps bool `yaml:"honor_timestamps"`
// A set of query parameters with which the target is scraped.
Params url.Values `yaml:"params,omitempty"`
// How frequently to scrape the targets of this scrape config.
ScrapeInterval model.Duration `yaml:"scrape_interval,omitempty"`
// The timeout for scraping targets of this config.
ScrapeTimeout model.Duration `yaml:"scrape_timeout,omitempty"`
// The HTTP resource path on which to fetch metrics from targets.
MetricsPath string `yaml:"metrics_path,omitempty"`
// The URL scheme with which to fetch metrics from targets.
Scheme string `yaml:"scheme,omitempty"`
// An uncompressed response body larger than this many bytes will cause the
// scrape to fail. 0 means no limit.
BodySizeLimit units.Base2Bytes `yaml:"body_size_limit,omitempty"`
// More than this many samples post metric-relabeling will cause the scrape to
// fail.
SampleLimit uint `yaml:"sample_limit,omitempty"`
// More than this many targets after the target relabeling will cause the
// scrapes to fail.
TargetLimit uint `yaml:"target_limit,omitempty"`
// More than this many labels post metric-relabeling will cause the scrape to
// fail.
LabelLimit uint `yaml:"label_limit,omitempty"`
// More than this label name length post metric-relabeling will cause the
// scrape to fail.
LabelNameLengthLimit uint `yaml:"label_name_length_limit,omitempty"`
// More than this label value length post metric-relabeling will cause the
// scrape to fail.
LabelValueLengthLimit uint `yaml:"label_value_length_limit,omitempty"`
// We cannot do proper Go type embedding below as the parser will then parse
// values arbitrarily into the overflow maps of further-down types.
ServiceDiscoveryConfigs discovery.Configs `yaml:"-"`
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
// List of target relabel configurations.
RelabelConfigs []*relabel.Config `yaml:"relabel_configs,omitempty"`
// List of metric relabel configurations.
MetricRelabelConfigs []*relabel.Config `yaml:"metric_relabel_configs,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (c *ScrapeConfig) SetDirectory(dir string) {
c.ServiceDiscoveryConfigs.SetDirectory(dir)
c.HTTPClientConfig.SetDirectory(dir)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *ScrapeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultScrapeConfig
if err := discovery.UnmarshalYAMLWithInlineConfigs(c, unmarshal); err != nil {
return err
}
if len(c.JobName) == 0 {
return errors.New("job_name is empty")
}
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
// We cannot make it a pointer as the parser panics for inlined pointer structs.
// Thus we just do its validation here.
if err := c.HTTPClientConfig.Validate(); err != nil {
return err
}
// Check for users putting URLs in target groups.
if len(c.RelabelConfigs) == 0 {
if err := checkStaticTargets(c.ServiceDiscoveryConfigs); err != nil {
return err
}
}
for _, rlcfg := range c.RelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null target relabeling rule in scrape config")
}
}
for _, rlcfg := range c.MetricRelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null metric relabeling rule in scrape config")
}
}
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
func (c *ScrapeConfig) MarshalYAML() (interface{}, error) {
return discovery.MarshalYAMLWithInlineConfigs(c)
}
// StorageConfig configures runtime reloadable configuration options.
type StorageConfig struct {
TSDBConfig *TSDBConfig `yaml:"tsdb,omitempty"`
ExemplarsConfig *ExemplarsConfig `yaml:"exemplars,omitempty"`
}
// TSDBConfig configures runtime reloadable configuration options.
type TSDBConfig struct {
// OutOfOrderTimeWindow sets how long back in time an out-of-order sample can be inserted
// into the TSDB. This flag is typically set while unmarshaling the configuration file and translating
// OutOfOrderTimeWindowFlag's duration. The unit of this flag is expected to be the same as any
// other timestamp in the TSDB.
OutOfOrderTimeWindow int64
// OutOfOrderTimeWindowFlag holds the parsed duration from the config file.
// During unmarshall, this is converted into milliseconds and stored in OutOfOrderTimeWindow.
// This should not be used directly and must be converted into OutOfOrderTimeWindow.
OutOfOrderTimeWindowFlag model.Duration `yaml:"out_of_order_time_window,omitempty"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (t *TSDBConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*t = TSDBConfig{}
type plain TSDBConfig
if err := unmarshal((*plain)(t)); err != nil {
return err
}
t.OutOfOrderTimeWindow = time.Duration(t.OutOfOrderTimeWindowFlag).Milliseconds()
return nil
}
type TracingClientType string
const (
TracingClientHTTP TracingClientType = "http"
TracingClientGRPC TracingClientType = "grpc"
GzipCompression = "gzip"
)
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (t *TracingClientType) UnmarshalYAML(unmarshal func(interface{}) error) error {
*t = TracingClientType("")
type plain TracingClientType
if err := unmarshal((*plain)(t)); err != nil {
return err
}
if *t != TracingClientHTTP && *t != TracingClientGRPC {
return fmt.Errorf("expected tracing client type to be to be %s or %s, but got %s",
TracingClientHTTP, TracingClientGRPC, *t,
)
}
return nil
}
// TracingConfig configures the tracing options.
type TracingConfig struct {
ClientType TracingClientType `yaml:"client_type,omitempty"`
Endpoint string `yaml:"endpoint,omitempty"`
SamplingFraction float64 `yaml:"sampling_fraction,omitempty"`
Insecure bool `yaml:"insecure,omitempty"`
TLSConfig config.TLSConfig `yaml:"tls_config,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
Compression string `yaml:"compression,omitempty"`
Timeout model.Duration `yaml:"timeout,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (t *TracingConfig) SetDirectory(dir string) {
t.TLSConfig.SetDirectory(dir)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (t *TracingConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*t = TracingConfig{
ClientType: TracingClientGRPC,
}
type plain TracingConfig
if err := unmarshal((*plain)(t)); err != nil {
return err
}
if err := validateHeadersForTracing(t.Headers); err != nil {
return err
}
if t.Endpoint == "" {
return errors.New("tracing endpoint must be set")
}
if t.Compression != "" && t.Compression != GzipCompression {
return fmt.Errorf("invalid compression type %s provided, valid options: %s",
t.Compression, GzipCompression)
}
return nil
}
// ExemplarsConfig configures runtime reloadable configuration options.
type ExemplarsConfig struct {
// MaxExemplars sets the size, in # of exemplars stored, of the single circular buffer used to store exemplars in memory.
// Use a value of 0 or less than 0 to disable the storage without having to restart Prometheus.
MaxExemplars int64 `yaml:"max_exemplars,omitempty"`
}
// AlertingConfig configures alerting and alertmanager related configs.
type AlertingConfig struct {
AlertRelabelConfigs []*relabel.Config `yaml:"alert_relabel_configs,omitempty"`
AlertmanagerConfigs AlertmanagerConfigs `yaml:"alertmanagers,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (c *AlertingConfig) SetDirectory(dir string) {
for _, c := range c.AlertmanagerConfigs {
c.SetDirectory(dir)
}
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *AlertingConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Create a clean global config as the previous one was already populated
// by the default due to the YAML parser behavior for empty blocks.
*c = AlertingConfig{}
type plain AlertingConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
for _, rlcfg := range c.AlertRelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null alert relabeling rule")
}
}
return nil
}
// AlertmanagerConfigs is a slice of *AlertmanagerConfig.
type AlertmanagerConfigs []*AlertmanagerConfig
// ToMap converts a slice of *AlertmanagerConfig to a map.
func (a AlertmanagerConfigs) ToMap() map[string]*AlertmanagerConfig {
ret := make(map[string]*AlertmanagerConfig)
for i := range a {
ret[fmt.Sprintf("config-%d", i)] = a[i]
}
return ret
}
// AlertmanagerAPIVersion represents a version of the
// github.com/prometheus/alertmanager/api, e.g. 'v1' or 'v2'.
type AlertmanagerAPIVersion string
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (v *AlertmanagerAPIVersion) UnmarshalYAML(unmarshal func(interface{}) error) error {
*v = AlertmanagerAPIVersion("")
type plain AlertmanagerAPIVersion
if err := unmarshal((*plain)(v)); err != nil {
return err
}
for _, supportedVersion := range SupportedAlertmanagerAPIVersions {
if *v == supportedVersion {
return nil
}
}
return fmt.Errorf("expected Alertmanager api version to be one of %v but got %v", SupportedAlertmanagerAPIVersions, *v)
}
const (
// AlertmanagerAPIVersionV1 represents
// github.com/prometheus/alertmanager/api/v1.
AlertmanagerAPIVersionV1 AlertmanagerAPIVersion = "v1"
// AlertmanagerAPIVersionV2 represents
// github.com/prometheus/alertmanager/api/v2.
AlertmanagerAPIVersionV2 AlertmanagerAPIVersion = "v2"
)
var SupportedAlertmanagerAPIVersions = []AlertmanagerAPIVersion{
AlertmanagerAPIVersionV1, AlertmanagerAPIVersionV2,
}
// AlertmanagerConfig configures how Alertmanagers can be discovered and communicated with.
type AlertmanagerConfig struct {
// We cannot do proper Go type embedding below as the parser will then parse
// values arbitrarily into the overflow maps of further-down types.
ServiceDiscoveryConfigs discovery.Configs `yaml:"-"`
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
// The URL scheme to use when talking to Alertmanagers.
Scheme string `yaml:"scheme,omitempty"`
// Path prefix to add in front of the push endpoint path.
PathPrefix string `yaml:"path_prefix,omitempty"`
// The timeout used when sending alerts.
Timeout model.Duration `yaml:"timeout,omitempty"`
// The api version of Alertmanager.
APIVersion AlertmanagerAPIVersion `yaml:"api_version"`
// List of Alertmanager relabel configurations.
RelabelConfigs []*relabel.Config `yaml:"relabel_configs,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (c *AlertmanagerConfig) SetDirectory(dir string) {
c.ServiceDiscoveryConfigs.SetDirectory(dir)
c.HTTPClientConfig.SetDirectory(dir)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *AlertmanagerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultAlertmanagerConfig
if err := discovery.UnmarshalYAMLWithInlineConfigs(c, unmarshal); err != nil {
return err
}
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
// We cannot make it a pointer as the parser panics for inlined pointer structs.
// Thus we just do its validation here.
if err := c.HTTPClientConfig.Validate(); err != nil {
return err
}
// Check for users putting URLs in target groups.
if len(c.RelabelConfigs) == 0 {
if err := checkStaticTargets(c.ServiceDiscoveryConfigs); err != nil {
return err
}
}
for _, rlcfg := range c.RelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null Alertmanager target relabeling rule")
}
}
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
func (c *AlertmanagerConfig) MarshalYAML() (interface{}, error) {
return discovery.MarshalYAMLWithInlineConfigs(c)
}
func checkStaticTargets(configs discovery.Configs) error {
for _, cfg := range configs {
sc, ok := cfg.(discovery.StaticConfig)
if !ok {
continue
}
for _, tg := range sc {
for _, t := range tg.Targets {
if err := CheckTargetAddress(t[model.AddressLabel]); err != nil {
return err
}
}
}
}
return nil
}
// CheckTargetAddress checks if target address is valid.
func CheckTargetAddress(address model.LabelValue) error {
// For now check for a URL, we may want to expand this later.
if strings.Contains(string(address), "/") {
return fmt.Errorf("%q is not a valid hostname", address)
}
return nil
}
// RemoteWriteConfig is the configuration for writing to remote storage.
type RemoteWriteConfig struct {
URL *config.URL `yaml:"url"`
RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
WriteRelabelConfigs []*relabel.Config `yaml:"write_relabel_configs,omitempty"`
Name string `yaml:"name,omitempty"`
SendExemplars bool `yaml:"send_exemplars,omitempty"`
SendNativeHistograms bool `yaml:"send_native_histograms,omitempty"`
// We cannot do proper Go type embedding below as the parser will then parse
// values arbitrarily into the overflow maps of further-down types.
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
QueueConfig QueueConfig `yaml:"queue_config,omitempty"`
MetadataConfig MetadataConfig `yaml:"metadata_config,omitempty"`
SigV4Config *sigv4.SigV4Config `yaml:"sigv4,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (c *RemoteWriteConfig) SetDirectory(dir string) {
c.HTTPClientConfig.SetDirectory(dir)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultRemoteWriteConfig
type plain RemoteWriteConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.URL == nil {
return errors.New("url for remote_write is empty")
}
for _, rlcfg := range c.WriteRelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null relabeling rule in remote write config")
}
}
if err := validateHeaders(c.Headers); err != nil {
return err
}
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
// We cannot make it a pointer as the parser panics for inlined pointer structs.
// Thus we just do its validation here.
if err := c.HTTPClientConfig.Validate(); err != nil {
return err
}
httpClientConfigAuthEnabled := c.HTTPClientConfig.BasicAuth != nil ||
c.HTTPClientConfig.Authorization != nil || c.HTTPClientConfig.OAuth2 != nil
if httpClientConfigAuthEnabled && c.SigV4Config != nil {
return fmt.Errorf("at most one of basic_auth, authorization, oauth2, & sigv4 must be configured")
}
return nil
}
func validateHeadersForTracing(headers map[string]string) error {
for header := range headers {
if strings.ToLower(header) == "authorization" {
return errors.New("custom authorization header configuration is not yet supported")
}
if _, ok := reservedHeaders[strings.ToLower(header)]; ok {
return fmt.Errorf("%s is a reserved header. It must not be changed", header)
}
}
return nil
}
func validateHeaders(headers map[string]string) error {
for header := range headers {
if strings.ToLower(header) == "authorization" {
return errors.New("authorization header must be changed via the basic_auth, authorization, oauth2, or sigv4 parameter")
}
if _, ok := reservedHeaders[strings.ToLower(header)]; ok {
return fmt.Errorf("%s is a reserved header. It must not be changed", header)
}
}
return nil
}
// QueueConfig is the configuration for the queue used to write to remote
// storage.
type QueueConfig struct {
// Number of samples to buffer per shard before we block. Defaults to
// MaxSamplesPerSend.
Capacity int `yaml:"capacity,omitempty"`
// Max number of shards, i.e. amount of concurrency.
MaxShards int `yaml:"max_shards,omitempty"`
// Min number of shards, i.e. amount of concurrency.
MinShards int `yaml:"min_shards,omitempty"`
// Maximum number of samples per send.
MaxSamplesPerSend int `yaml:"max_samples_per_send,omitempty"`
// Maximum time sample will wait in buffer.
BatchSendDeadline model.Duration `yaml:"batch_send_deadline,omitempty"`
// On recoverable errors, backoff exponentially.
MinBackoff model.Duration `yaml:"min_backoff,omitempty"`
MaxBackoff model.Duration `yaml:"max_backoff,omitempty"`
RetryOnRateLimit bool `yaml:"retry_on_http_429,omitempty"`
}
// MetadataConfig is the configuration for sending metadata to remote
// storage.
type MetadataConfig struct {
// Send controls whether we send metric metadata to remote storage.
Send bool `yaml:"send"`
// SendInterval controls how frequently we send metric metadata.
SendInterval model.Duration `yaml:"send_interval"`
// Maximum number of samples per send.
MaxSamplesPerSend int `yaml:"max_samples_per_send,omitempty"`
}
// RemoteReadConfig is the configuration for reading from remote storage.
type RemoteReadConfig struct {
URL *config.URL `yaml:"url"`
RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
ReadRecent bool `yaml:"read_recent,omitempty"`
Name string `yaml:"name,omitempty"`
// We cannot do proper Go type embedding below as the parser will then parse
// values arbitrarily into the overflow maps of further-down types.
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
// RequiredMatchers is an optional list of equality matchers which have to
// be present in a selector to query the remote read endpoint.
RequiredMatchers model.LabelSet `yaml:"required_matchers,omitempty"`
// Whether to use the external labels as selectors for the remote read endpoint.
FilterExternalLabels bool `yaml:"filter_external_labels,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
func (c *RemoteReadConfig) SetDirectory(dir string) {
c.HTTPClientConfig.SetDirectory(dir)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *RemoteReadConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultRemoteReadConfig
type plain RemoteReadConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.URL == nil {
return errors.New("url for remote_read is empty")
}
if err := validateHeaders(c.Headers); err != nil {
return err
}
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
// We cannot make it a pointer as the parser panics for inlined pointer structs.
// Thus we just do its validation here.
return c.HTTPClientConfig.Validate()
}

View File

@@ -0,0 +1,267 @@
# Service Discovery
This directory contains the service discovery (SD) component of Prometheus.
## Design of a Prometheus SD
There are many requests to add new SDs to Prometheus, this section looks at
what makes a good SD and covers some of the common implementation issues.
### Does this make sense as an SD?
The first question to be asked is does it make sense to add this particular
SD? An SD mechanism should be reasonably well established, and at a minimum in
use across multiple organizations. It should allow discovering of machines
and/or services running somewhere. When exactly an SD is popular enough to
justify being added to Prometheus natively is an open question.
Note: As part of lifting the past moratorium on new SD implementations it was
agreed that, in addition to the existing requirements, new service discovery
implementations will be required to have a committed maintainer with push access (i.e., on -team).
It should not be a brand new SD mechanism, or a variant of an established
mechanism. We want to integrate Prometheus with the SD that's already there in
your infrastructure, not invent yet more ways to do service discovery. We also
do not add mechanisms to work around users lacking service discovery and/or
configuration management infrastructure.
SDs that merely discover other applications running the same software (e.g.
talk to one Kafka or Cassandra server to find the others) are not service
discovery. In that case the SD you should be looking at is whatever decides
that a machine is going to be a Kafka server, likely a machine database or
configuration management system.
If something is particularly custom or unusual, `file_sd` is the generic
mechanism provided for users to hook in. Generally with Prometheus we offer a
single generic mechanism for things with infinite variations, rather than
trying to support everything natively (see also, alertmanager webhook, remote
read, remote write, node exporter textfile collector). For example anything
that would involve talking to a relational database should use `file_sd`
instead.
For configuration management systems like Chef, while they do have a
database/API that'd in principle make sense to talk to for service discovery,
the idiomatic approach is to use Chef's templating facilities to write out a
file for use with `file_sd`.
### Mapping from SD to Prometheus
The general principle with SD is to extract all the potentially useful
information we can out of the SD, and let the user choose what they need of it
using
[relabelling](https://prometheus.io/docs/operating/configuration/#<relabel_config>).
This information is generally termed metadata.
Metadata is exposed as a set of key/value pairs (labels) per target. The keys
are prefixed with `__meta_<sdname>_<key>`, and there should also be an `__address__`
label with the host:port of the target (preferably an IP address to avoid DNS
lookups). No other labelnames should be exposed.
It is very common for initial pull requests for new SDs to include hardcoded
assumptions that make sense for the author's setup. SD should be generic,
any customisation should be handled via relabelling. There should be basically
no business logic, filtering, or transformations of the data from the SD beyond
that which is needed to fit it into the metadata data model.
Arrays (e.g. a list of tags) should be converted to a single label with the
array values joined with a comma. Also prefix and suffix the value with a
comma. So for example the array `[a, b, c]` would become `,a,b,c,`. As
relabelling regexes are fully anchored, this makes it easier to write correct
regexes against (`.*,a,.*` works no matter where `a` appears in the list). The
canonical example of this is `__meta_consul_tags`.
Maps, hashes and other forms of key/value pairs should be all prefixed and
exposed as labels. For example for EC2 tags, there would be
`__meta_ec2_tag_Description=mydescription` for the Description tag. Labelnames
may only contain `[_a-zA-Z0-9]`, sanitize by replacing with underscores as needed.
For targets with multiple potential ports, you can a) expose them as a list, b)
if they're named expose them as a map or c) expose them each as their own
target. Kubernetes SD takes the target per port approach. a) and b) can be
combined.
For machine-like SDs (OpenStack, EC2, Kubernetes to some extent) there may
be multiple network interfaces for a target. Thus far reporting the details
of only the first/primary network interface has sufficed.
### Other implementation considerations
SDs are intended to dump all possible targets. For example the optional use of
EC2 service discovery would be to take the entire region's worth of EC2
instances it provides and do everything needed in one `scrape_config`. For
large deployments where you are only interested in a small proportion of the
returned targets, this may cause performance issues. If this occurs it is
acceptable to also offer filtering via whatever mechanisms the SD exposes. For
EC2 that would be the `Filter` option on `DescribeInstances`. Keep in mind that
this is a performance optimisation, it should be possible to do the same
filtering using relabelling alone. As with SD generally, we do not invent new
ways to filter targets (that is what relabelling is for), merely offer up
whatever functionality the SD itself offers.
It is a general rule with Prometheus that all configuration comes from the
configuration file. While the libraries you use to talk to the SD may also
offer other mechanisms for providing configuration/authentication under the
covers (EC2's use of environment variables being a prime example), using your SD
mechanism should not require this. Put another way, your SD implementation
should not read environment variables or files to obtain configuration.
Some SD mechanisms have rate limits that make them challenging to use. As an
example we have unfortunately had to reject Amazon ECS service discovery due to
the rate limits being so low that it would not be usable for anything beyond
small setups.
If a system offers multiple distinct types of SD, select which is in use with a
configuration option rather than returning them all from one mega SD that
requires relabelling to select just the one you want. So far we have only seen
this with Kubernetes. When a single SD with a selector vs. multiple distinct
SDs makes sense is an open question.
If there is a failure while processing talking to the SD, abort rather than
returning partial data. It is better to work from stale targets than partial
or incorrect metadata.
The information obtained from service discovery is not considered sensitive
security wise. Do not return secrets in metadata, anyone with access to
the Prometheus server will be able to see them.
## Writing an SD mechanism
### The SD interface
A Service Discovery (SD) mechanism has to discover targets and provide them to Prometheus. We expect similar targets to be grouped together, in the form of a [target group](https://pkg.go.dev/github.com/prometheus/prometheus/discovery/targetgroup#Group). The SD mechanism sends the targets down to prometheus as list of target groups.
An SD mechanism has to implement the `Discoverer` Interface:
```go
type Discoverer interface {
Run(ctx context.Context, up chan<- []*targetgroup.Group)
}
```
Prometheus will call the `Run()` method on a provider to initialize the discovery mechanism. The mechanism will then send *all* the target groups into the channel.
Now the mechanism will watch for changes. For each update it can send all target groups, or only changed and new target groups, down the channel. `Manager` will handle
both cases.
For example if we had a discovery mechanism and it retrieves the following groups:
```go
[]targetgroup.Group{
{
Targets: []model.LabelSet{
{
"__instance__": "10.11.150.1:7870",
"hostname": "demo-target-1",
"test": "simple-test",
},
{
"__instance__": "10.11.150.4:7870",
"hostname": "demo-target-2",
"test": "simple-test",
},
},
Labels: model.LabelSet{
"job": "mysql",
},
"Source": "file1",
},
{
Targets: []model.LabelSet{
{
"__instance__": "10.11.122.11:6001",
"hostname": "demo-postgres-1",
"test": "simple-test",
},
{
"__instance__": "10.11.122.15:6001",
"hostname": "demo-postgres-2",
"test": "simple-test",
},
},
Labels: model.LabelSet{
"job": "postgres",
},
"Source": "file2",
},
}
```
Here there are two target groups one group with source `file1` and another with `file2`. The grouping is implementation specific and could even be one target per group. But, one has to make sure every target group sent by an SD instance should have a `Source` which is unique across all the target groups of that SD instance.
In this case, both the target groups are sent down the channel the first time `Run()` is called. Now, for an update, we need to send the whole _changed_ target group down the channel. i.e, if the target with `hostname: demo-postgres-2` goes away, we send:
```go
&targetgroup.Group{
Targets: []model.LabelSet{
{
"__instance__": "10.11.122.11:6001",
"hostname": "demo-postgres-1",
"test": "simple-test",
},
},
Labels: model.LabelSet{
"job": "postgres",
},
"Source": "file2",
}
```
down the channel.
If all the targets in a group go away, we need to send the target groups with empty `Targets` down the channel. i.e, if all targets with `job: postgres` go away, we send:
```go
&targetgroup.Group{
Targets: nil,
"Source": "file2",
}
```
down the channel.
### The Config interface
Now that your service discovery mechanism is ready to discover targets, you must help
Prometheus discover it. This is done by implementing the `discovery.Config` interface
and registering it with `discovery.RegisterConfig` in an init function of your package.
```go
type Config interface {
// Name returns the name of the discovery mechanism.
Name() string
// NewDiscoverer returns a Discoverer for the Config
// with the given DiscovererOptions.
NewDiscoverer(DiscovererOptions) (Discoverer, error)
}
type DiscovererOptions struct {
Logger log.Logger
}
```
The value returned by `Name()` should be short, descriptive, lowercase, and unique.
It's used to tag the provided `Logger` and as the part of the YAML key for your SD
mechanism's list of configs in `scrape_config` and `alertmanager_config`
(e.g. `${NAME}_sd_configs`).
### New Service Discovery Check List
Here are some non-obvious parts of adding service discoveries that need to be verified:
- Validate that discovery configs can be DeepEqualled by adding them to
`config/testdata/conf.good.yml` and to the associated tests.
- If the config contains file paths directly or indirectly (e.g. with a TLSConfig or
HTTPClientConfig field), then it must implement `config.DirectorySetter`.
- Import your SD package from `prometheus/discovery/install`. The install package is
imported from `main` to register all builtin SD mechanisms.
- List the service discovery in both `<scrape_config>` and
`<alertmanager_config>` in `docs/configuration/configuration.md`.
<!-- TODO: Add best-practices -->
### Examples of Service Discovery pull requests
The examples given might become out of date but should give a good impression about the areas touched by a new service discovery.
- [Eureka](https://github.com/prometheus/prometheus/pull/3369)

View File

@@ -0,0 +1,121 @@
// Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package discovery
import (
"context"
"reflect"
"github.com/go-kit/log"
"github.com/prometheus/common/config"
"github.com/prometheus/prometheus/discovery/targetgroup"
)
// Discoverer provides information about target groups. It maintains a set
// of sources from which TargetGroups can originate. Whenever a discovery provider
// detects a potential change, it sends the TargetGroup through its channel.
//
// Discoverer does not know if an actual change happened.
// It does guarantee that it sends the new TargetGroup whenever a change happens.
//
// Discoverers should initially send a full set of all discoverable TargetGroups.
type Discoverer interface {
// Run hands a channel to the discovery provider (Consul, DNS, etc.) through which
// it can send updated target groups. It must return when the context is canceled.
// It should not close the update channel on returning.
Run(ctx context.Context, up chan<- []*targetgroup.Group)
}
// DiscovererOptions provides options for a Discoverer.
type DiscovererOptions struct {
Logger log.Logger
// Extra HTTP client options to expose to Discoverers. This field may be
// ignored; Discoverer implementations must opt-in to reading it.
HTTPClientOptions []config.HTTPClientOption
}
// A Config provides the configuration and constructor for a Discoverer.
type Config interface {
// Name returns the name of the discovery mechanism.
Name() string
// NewDiscoverer returns a Discoverer for the Config
// with the given DiscovererOptions.
NewDiscoverer(DiscovererOptions) (Discoverer, error)
}
// Configs is a slice of Config values that uses custom YAML marshaling and unmarshaling
// to represent itself as a mapping of the Config values grouped by their types.
type Configs []Config
// SetDirectory joins any relative file paths with dir.
func (c *Configs) SetDirectory(dir string) {
for _, c := range *c {
if v, ok := c.(config.DirectorySetter); ok {
v.SetDirectory(dir)
}
}
}
// UnmarshalYAML implements yaml.Unmarshaler.
func (c *Configs) UnmarshalYAML(unmarshal func(interface{}) error) error {
cfgTyp := getConfigType(configsType)
cfgPtr := reflect.New(cfgTyp)
cfgVal := cfgPtr.Elem()
if err := unmarshal(cfgPtr.Interface()); err != nil {
return replaceYAMLTypeError(err, cfgTyp, configsType)
}
var err error
*c, err = readConfigs(cfgVal, 0)
return err
}
// MarshalYAML implements yaml.Marshaler.
func (c Configs) MarshalYAML() (interface{}, error) {
cfgTyp := getConfigType(configsType)
cfgPtr := reflect.New(cfgTyp)
cfgVal := cfgPtr.Elem()
if err := writeConfigs(cfgVal, c); err != nil {
return nil, err
}
return cfgPtr.Interface(), nil
}
// A StaticConfig is a Config that provides a static list of targets.
type StaticConfig []*targetgroup.Group
// Name returns the name of the service discovery mechanism.
func (StaticConfig) Name() string { return "static" }
// NewDiscoverer returns a Discoverer for the Config.
func (c StaticConfig) NewDiscoverer(DiscovererOptions) (Discoverer, error) {
return staticDiscoverer(c), nil
}
type staticDiscoverer []*targetgroup.Group
func (c staticDiscoverer) Run(ctx context.Context, up chan<- []*targetgroup.Group) {
// TODO: existing implementation closes up chan, but documentation explicitly forbids it...?
defer close(up)
select {
case <-ctx.Done():
case up <- c:
}
}

View File

@@ -0,0 +1,478 @@
// Copyright 2016 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package discovery
import (
"context"
"fmt"
"reflect"
"sync"
"time"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/prometheus/discovery/targetgroup"
)
var (
failedConfigs = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "prometheus_sd_failed_configs",
Help: "Current number of service discovery configurations that failed to load.",
},
[]string{"name"},
)
discoveredTargets = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "prometheus_sd_discovered_targets",
Help: "Current number of discovered targets.",
},
[]string{"name", "config"},
)
receivedUpdates = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "prometheus_sd_received_updates_total",
Help: "Total number of update events received from the SD providers.",
},
[]string{"name"},
)
delayedUpdates = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "prometheus_sd_updates_delayed_total",
Help: "Total number of update events that couldn't be sent immediately.",
},
[]string{"name"},
)
sentUpdates = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "prometheus_sd_updates_total",
Help: "Total number of update events sent to the SD consumers.",
},
[]string{"name"},
)
)
func RegisterMetrics() {
prometheus.MustRegister(failedConfigs, discoveredTargets, receivedUpdates, delayedUpdates, sentUpdates)
}
type poolKey struct {
setName string
provider string
}
// Provider holds a Discoverer instance, its configuration, cancel func and its subscribers.
type Provider struct {
name string
d Discoverer
config interface{}
cancel context.CancelFunc
// done should be called after cleaning up resources associated with cancelled provider.
done func()
mu sync.RWMutex
subs map[string]struct{}
// newSubs is used to temporary store subs to be used upon config reload completion.
newSubs map[string]struct{}
}
// Discoverer return the Discoverer of the provider
func (p *Provider) Discoverer() Discoverer {
return p.d
}
// IsStarted return true if Discoverer is started.
func (p *Provider) IsStarted() bool {
return p.cancel != nil
}
func (p *Provider) Config() interface{} {
return p.config
}
// NewManager is the Discovery Manager constructor.
func NewManager(ctx context.Context, logger log.Logger, options ...func(*Manager)) *Manager {
if logger == nil {
logger = log.NewNopLogger()
}
mgr := &Manager{
logger: logger,
syncCh: make(chan map[string][]*targetgroup.Group),
targets: make(map[poolKey]map[string]*targetgroup.Group),
ctx: ctx,
updatert: 5 * time.Second,
triggerSend: make(chan struct{}, 1),
}
for _, option := range options {
option(mgr)
}
return mgr
}
// Name sets the name of the manager.
func Name(n string) func(*Manager) {
return func(m *Manager) {
m.mtx.Lock()
defer m.mtx.Unlock()
m.name = n
}
}
// HTTPClientOptions sets the list of HTTP client options to expose to
// Discoverers. It is up to Discoverers to choose to use the options provided.
func HTTPClientOptions(opts ...config.HTTPClientOption) func(*Manager) {
return func(m *Manager) {
m.httpOpts = opts
}
}
// Manager maintains a set of discovery providers and sends each update to a map channel.
// Targets are grouped by the target set name.
type Manager struct {
logger log.Logger
name string
httpOpts []config.HTTPClientOption
mtx sync.RWMutex
ctx context.Context
// Some Discoverers(e.g. k8s) send only the updates for a given target group,
// so we use map[tg.Source]*targetgroup.Group to know which group to update.
targets map[poolKey]map[string]*targetgroup.Group
targetsMtx sync.Mutex
// providers keeps track of SD providers.
providers []*Provider
// The sync channel sends the updates as a map where the key is the job value from the scrape config.
syncCh chan map[string][]*targetgroup.Group
// How long to wait before sending updates to the channel. The variable
// should only be modified in unit tests.
updatert time.Duration
// The triggerSend channel signals to the Manager that new updates have been received from providers.
triggerSend chan struct{}
// lastProvider counts providers registered during Manager's lifetime.
lastProvider uint
}
// Providers returns the currently configured SD providers.
func (m *Manager) Providers() []*Provider {
return m.providers
}
// Run starts the background processing.
func (m *Manager) Run() error {
go m.sender()
for range m.ctx.Done() {
m.cancelDiscoverers()
return m.ctx.Err()
}
return nil
}
// SyncCh returns a read only channel used by all the clients to receive target updates.
func (m *Manager) SyncCh() <-chan map[string][]*targetgroup.Group {
return m.syncCh
}
// ApplyConfig checks if discovery provider with supplied config is already running and keeps them as is.
// Remaining providers are then stopped and new required providers are started using the provided config.
func (m *Manager) ApplyConfig(cfg map[string]Configs) error {
m.mtx.Lock()
defer m.mtx.Unlock()
var failedCount int
for name, scfg := range cfg {
failedCount += m.registerProviders(scfg, name)
}
failedConfigs.WithLabelValues(m.name).Set(float64(failedCount))
var (
wg sync.WaitGroup
// keep shows if we keep any providers after reload.
keep bool
newProviders []*Provider
)
for _, prov := range m.providers {
// Cancel obsolete providers.
if len(prov.newSubs) == 0 {
wg.Add(1)
prov.done = func() {
wg.Done()
}
prov.cancel()
continue
}
newProviders = append(newProviders, prov)
// refTargets keeps reference targets used to populate new subs' targets
var refTargets map[string]*targetgroup.Group
prov.mu.Lock()
m.targetsMtx.Lock()
for s := range prov.subs {
keep = true
refTargets = m.targets[poolKey{s, prov.name}]
// Remove obsolete subs' targets.
if _, ok := prov.newSubs[s]; !ok {
delete(m.targets, poolKey{s, prov.name})
discoveredTargets.DeleteLabelValues(m.name, s)
}
}
// Set metrics and targets for new subs.
for s := range prov.newSubs {
if _, ok := prov.subs[s]; !ok {
discoveredTargets.WithLabelValues(m.name, s).Set(0)
}
if l := len(refTargets); l > 0 {
m.targets[poolKey{s, prov.name}] = make(map[string]*targetgroup.Group, l)
for k, v := range refTargets {
m.targets[poolKey{s, prov.name}][k] = v
}
}
}
m.targetsMtx.Unlock()
prov.subs = prov.newSubs
prov.newSubs = map[string]struct{}{}
prov.mu.Unlock()
if !prov.IsStarted() {
m.startProvider(m.ctx, prov)
}
}
// Currently downstream managers expect full target state upon config reload, so we must oblige.
// While startProvider does pull the trigger, it may take some time to do so, therefore
// we pull the trigger as soon as possible so that downstream managers can populate their state.
// See https://github.com/prometheus/prometheus/pull/8639 for details.
if keep {
select {
case m.triggerSend <- struct{}{}:
default:
}
}
m.providers = newProviders
wg.Wait()
return nil
}
// StartCustomProvider is used for sdtool. Only use this if you know what you're doing.
func (m *Manager) StartCustomProvider(ctx context.Context, name string, worker Discoverer) {
p := &Provider{
name: name,
d: worker,
subs: map[string]struct{}{
name: {},
},
}
m.providers = append(m.providers, p)
m.startProvider(ctx, p)
}
func (m *Manager) startProvider(ctx context.Context, p *Provider) {
level.Debug(m.logger).Log("msg", "Starting provider", "provider", p.name, "subs", fmt.Sprintf("%v", p.subs))
ctx, cancel := context.WithCancel(ctx)
updates := make(chan []*targetgroup.Group)
p.cancel = cancel
go p.d.Run(ctx, updates)
go m.updater(ctx, p, updates)
}
// cleaner cleans resources associated with provider.
func (m *Manager) cleaner(p *Provider) {
m.targetsMtx.Lock()
p.mu.RLock()
for s := range p.subs {
delete(m.targets, poolKey{s, p.name})
}
p.mu.RUnlock()
m.targetsMtx.Unlock()
if p.done != nil {
p.done()
}
}
func (m *Manager) updater(ctx context.Context, p *Provider, updates chan []*targetgroup.Group) {
// Ensure targets from this provider are cleaned up.
defer m.cleaner(p)
for {
select {
case <-ctx.Done():
return
case tgs, ok := <-updates:
receivedUpdates.WithLabelValues(m.name).Inc()
if !ok {
level.Debug(m.logger).Log("msg", "Discoverer channel closed", "provider", p.name)
// Wait for provider cancellation to ensure targets are cleaned up when expected.
<-ctx.Done()
return
}
p.mu.RLock()
for s := range p.subs {
m.updateGroup(poolKey{setName: s, provider: p.name}, tgs)
}
p.mu.RUnlock()
select {
case m.triggerSend <- struct{}{}:
default:
}
}
}
}
func (m *Manager) sender() {
ticker := time.NewTicker(m.updatert)
defer ticker.Stop()
for {
select {
case <-m.ctx.Done():
return
case <-ticker.C: // Some discoverers send updates too often, so we throttle these with the ticker.
select {
case <-m.triggerSend:
sentUpdates.WithLabelValues(m.name).Inc()
select {
case m.syncCh <- m.allGroups():
default:
delayedUpdates.WithLabelValues(m.name).Inc()
level.Debug(m.logger).Log("msg", "Discovery receiver's channel was full so will retry the next cycle")
select {
case m.triggerSend <- struct{}{}:
default:
}
}
default:
}
}
}
}
func (m *Manager) cancelDiscoverers() {
m.mtx.RLock()
defer m.mtx.RUnlock()
for _, p := range m.providers {
if p.cancel != nil {
p.cancel()
}
}
}
func (m *Manager) updateGroup(poolKey poolKey, tgs []*targetgroup.Group) {
m.targetsMtx.Lock()
defer m.targetsMtx.Unlock()
if _, ok := m.targets[poolKey]; !ok {
m.targets[poolKey] = make(map[string]*targetgroup.Group)
}
for _, tg := range tgs {
if tg != nil { // Some Discoverers send nil target group so need to check for it to avoid panics.
m.targets[poolKey][tg.Source] = tg
}
}
}
func (m *Manager) allGroups() map[string][]*targetgroup.Group {
tSets := map[string][]*targetgroup.Group{}
n := map[string]int{}
m.targetsMtx.Lock()
defer m.targetsMtx.Unlock()
for pkey, tsets := range m.targets {
for _, tg := range tsets {
// Even if the target group 'tg' is empty we still need to send it to the 'Scrape manager'
// to signal that it needs to stop all scrape loops for this target set.
tSets[pkey.setName] = append(tSets[pkey.setName], tg)
n[pkey.setName] += len(tg.Targets)
}
}
for setName, v := range n {
discoveredTargets.WithLabelValues(m.name, setName).Set(float64(v))
}
return tSets
}
// registerProviders returns a number of failed SD config.
func (m *Manager) registerProviders(cfgs Configs, setName string) int {
var (
failed int
added bool
)
add := func(cfg Config) {
for _, p := range m.providers {
if reflect.DeepEqual(cfg, p.config) {
p.newSubs[setName] = struct{}{}
added = true
return
}
}
typ := cfg.Name()
d, err := cfg.NewDiscoverer(DiscovererOptions{
Logger: log.With(m.logger, "discovery", typ, "config", setName),
HTTPClientOptions: m.httpOpts,
})
if err != nil {
level.Error(m.logger).Log("msg", "Cannot create service discovery", "err", err, "type", typ, "config", setName)
failed++
return
}
m.providers = append(m.providers, &Provider{
name: fmt.Sprintf("%s/%d", typ, m.lastProvider),
d: d,
config: cfg,
newSubs: map[string]struct{}{
setName: {},
},
})
m.lastProvider++
added = true
}
for _, cfg := range cfgs {
add(cfg)
}
if !added {
// Add an empty target group to force the refresh of the corresponding
// scrape pool and to notify the receiver that this target set has no
// current targets.
// It can happen because the combined set of SD configurations is empty
// or because we fail to instantiate all the SD configurations.
add(StaticConfig{{}})
}
return failed
}
// StaticProvider holds a list of target groups that never change.
type StaticProvider struct {
TargetGroups []*targetgroup.Group
}
// Run implements the Worker interface.
func (sd *StaticProvider) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
// We still have to consider that the consumer exits right away in which case
// the context will be canceled.
select {
case ch <- sd.TargetGroups:
case <-ctx.Done():
}
close(ch)
}

View File

@@ -0,0 +1,260 @@
// Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package discovery
import (
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"gopkg.in/yaml.v2"
"github.com/prometheus/prometheus/discovery/targetgroup"
)
const (
configFieldPrefix = "AUTO_DISCOVERY_"
staticConfigsKey = "static_configs"
staticConfigsFieldName = configFieldPrefix + staticConfigsKey
)
var (
configNames = make(map[string]Config)
configFieldNames = make(map[reflect.Type]string)
configFields []reflect.StructField
configTypesMu sync.Mutex
configTypes = make(map[reflect.Type]reflect.Type)
emptyStructType = reflect.TypeOf(struct{}{})
configsType = reflect.TypeOf(Configs{})
)
// RegisterConfig registers the given Config type for YAML marshaling and unmarshaling.
func RegisterConfig(config Config) {
registerConfig(config.Name()+"_sd_configs", reflect.TypeOf(config), config)
}
func init() {
// N.B.: static_configs is the only Config type implemented by default.
// All other types are registered at init by their implementing packages.
elemTyp := reflect.TypeOf(&targetgroup.Group{})
registerConfig(staticConfigsKey, elemTyp, StaticConfig{})
}
func registerConfig(yamlKey string, elemType reflect.Type, config Config) {
name := config.Name()
if _, ok := configNames[name]; ok {
panic(fmt.Sprintf("discovery: Config named %q is already registered", name))
}
configNames[name] = config
fieldName := configFieldPrefix + yamlKey // Field must be exported.
configFieldNames[elemType] = fieldName
// Insert fields in sorted order.
i := sort.Search(len(configFields), func(k int) bool {
return fieldName < configFields[k].Name
})
configFields = append(configFields, reflect.StructField{}) // Add empty field at end.
copy(configFields[i+1:], configFields[i:]) // Shift fields to the right.
configFields[i] = reflect.StructField{ // Write new field in place.
Name: fieldName,
Type: reflect.SliceOf(elemType),
Tag: reflect.StructTag(`yaml:"` + yamlKey + `,omitempty"`),
}
}
func getConfigType(out reflect.Type) reflect.Type {
configTypesMu.Lock()
defer configTypesMu.Unlock()
if typ, ok := configTypes[out]; ok {
return typ
}
// Initial exported fields map one-to-one.
var fields []reflect.StructField
for i, n := 0, out.NumField(); i < n; i++ {
switch field := out.Field(i); {
case field.PkgPath == "" && field.Type != configsType:
fields = append(fields, field)
default:
fields = append(fields, reflect.StructField{
Name: "_" + field.Name, // Field must be unexported.
PkgPath: out.PkgPath(),
Type: emptyStructType,
})
}
}
// Append extra config fields on the end.
fields = append(fields, configFields...)
typ := reflect.StructOf(fields)
configTypes[out] = typ
return typ
}
// UnmarshalYAMLWithInlineConfigs helps implement yaml.Unmarshal for structs
// that have a Configs field that should be inlined.
func UnmarshalYAMLWithInlineConfigs(out interface{}, unmarshal func(interface{}) error) error {
outVal := reflect.ValueOf(out)
if outVal.Kind() != reflect.Ptr {
return fmt.Errorf("discovery: can only unmarshal into a struct pointer: %T", out)
}
outVal = outVal.Elem()
if outVal.Kind() != reflect.Struct {
return fmt.Errorf("discovery: can only unmarshal into a struct pointer: %T", out)
}
outTyp := outVal.Type()
cfgTyp := getConfigType(outTyp)
cfgPtr := reflect.New(cfgTyp)
cfgVal := cfgPtr.Elem()
// Copy shared fields (defaults) to dynamic value.
var configs *Configs
for i, n := 0, outVal.NumField(); i < n; i++ {
if outTyp.Field(i).Type == configsType {
configs = outVal.Field(i).Addr().Interface().(*Configs)
continue
}
if cfgTyp.Field(i).PkgPath != "" {
continue // Field is unexported: ignore.
}
cfgVal.Field(i).Set(outVal.Field(i))
}
if configs == nil {
return fmt.Errorf("discovery: Configs field not found in type: %T", out)
}
// Unmarshal into dynamic value.
if err := unmarshal(cfgPtr.Interface()); err != nil {
return replaceYAMLTypeError(err, cfgTyp, outTyp)
}
// Copy shared fields from dynamic value.
for i, n := 0, outVal.NumField(); i < n; i++ {
if cfgTyp.Field(i).PkgPath != "" {
continue // Field is unexported: ignore.
}
outVal.Field(i).Set(cfgVal.Field(i))
}
var err error
*configs, err = readConfigs(cfgVal, outVal.NumField())
return err
}
func readConfigs(structVal reflect.Value, startField int) (Configs, error) {
var (
configs Configs
targets []*targetgroup.Group
)
for i, n := startField, structVal.NumField(); i < n; i++ {
field := structVal.Field(i)
if field.Kind() != reflect.Slice {
panic("discovery: internal error: field is not a slice")
}
for k := 0; k < field.Len(); k++ {
val := field.Index(k)
if val.IsZero() || (val.Kind() == reflect.Ptr && val.Elem().IsZero()) {
key := configFieldNames[field.Type().Elem()]
key = strings.TrimPrefix(key, configFieldPrefix)
return nil, fmt.Errorf("empty or null section in %s", key)
}
switch c := val.Interface().(type) {
case *targetgroup.Group:
// Add index to the static config target groups for unique identification
// within scrape pool.
c.Source = strconv.Itoa(len(targets))
// Coalesce multiple static configs into a single static config.
targets = append(targets, c)
case Config:
configs = append(configs, c)
default:
panic("discovery: internal error: slice element is not a Config")
}
}
}
if len(targets) > 0 {
configs = append(configs, StaticConfig(targets))
}
return configs, nil
}
// MarshalYAMLWithInlineConfigs helps implement yaml.Marshal for structs
// that have a Configs field that should be inlined.
func MarshalYAMLWithInlineConfigs(in interface{}) (interface{}, error) {
inVal := reflect.ValueOf(in)
for inVal.Kind() == reflect.Ptr {
inVal = inVal.Elem()
}
inTyp := inVal.Type()
cfgTyp := getConfigType(inTyp)
cfgPtr := reflect.New(cfgTyp)
cfgVal := cfgPtr.Elem()
// Copy shared fields to dynamic value.
var configs *Configs
for i, n := 0, inTyp.NumField(); i < n; i++ {
if inTyp.Field(i).Type == configsType {
configs = inVal.Field(i).Addr().Interface().(*Configs)
}
if cfgTyp.Field(i).PkgPath != "" {
continue // Field is unexported: ignore.
}
cfgVal.Field(i).Set(inVal.Field(i))
}
if configs == nil {
return nil, fmt.Errorf("discovery: Configs field not found in type: %T", in)
}
if err := writeConfigs(cfgVal, *configs); err != nil {
return nil, err
}
return cfgPtr.Interface(), nil
}
func writeConfigs(structVal reflect.Value, configs Configs) error {
targets := structVal.FieldByName(staticConfigsFieldName).Addr().Interface().(*[]*targetgroup.Group)
for _, c := range configs {
if sc, ok := c.(StaticConfig); ok {
*targets = append(*targets, sc...)
continue
}
fieldName, ok := configFieldNames[reflect.TypeOf(c)]
if !ok {
return fmt.Errorf("discovery: cannot marshal unregistered Config type: %T", c)
}
field := structVal.FieldByName(fieldName)
field.Set(reflect.Append(field, reflect.ValueOf(c)))
}
return nil
}
func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
var e *yaml.TypeError
if errors.As(err, &e) {
oldStr := oldTyp.String()
newStr := newTyp.String()
for i, s := range e.Errors {
e.Errors[i] = strings.Replace(s, oldStr, newStr, -1)
}
}
return err
}

View File

@@ -0,0 +1,108 @@
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package targetgroup
import (
"bytes"
"encoding/json"
"github.com/prometheus/common/model"
)
// Group is a set of targets with a common label set(production , test, staging etc.).
type Group struct {
// Targets is a list of targets identified by a label set. Each target is
// uniquely identifiable in the group by its address label.
Targets []model.LabelSet
// Labels is a set of labels that is common across all targets in the group.
Labels model.LabelSet
// Source is an identifier that describes a group of targets.
Source string
}
func (tg Group) String() string {
return tg.Source
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (tg *Group) UnmarshalYAML(unmarshal func(interface{}) error) error {
g := struct {
Targets []string `yaml:"targets"`
Labels model.LabelSet `yaml:"labels"`
}{}
if err := unmarshal(&g); err != nil {
return err
}
tg.Targets = make([]model.LabelSet, 0, len(g.Targets))
for _, t := range g.Targets {
tg.Targets = append(tg.Targets, model.LabelSet{
model.AddressLabel: model.LabelValue(t),
})
}
tg.Labels = g.Labels
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
func (tg Group) MarshalYAML() (interface{}, error) {
g := &struct {
Targets []string `yaml:"targets"`
Labels model.LabelSet `yaml:"labels,omitempty"`
}{
Targets: make([]string, 0, len(tg.Targets)),
Labels: tg.Labels,
}
for _, t := range tg.Targets {
g.Targets = append(g.Targets, string(t[model.AddressLabel]))
}
return g, nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (tg *Group) UnmarshalJSON(b []byte) error {
g := struct {
Targets []string `json:"targets"`
Labels model.LabelSet `json:"labels"`
}{}
dec := json.NewDecoder(bytes.NewReader(b))
dec.DisallowUnknownFields()
if err := dec.Decode(&g); err != nil {
return err
}
tg.Targets = make([]model.LabelSet, 0, len(g.Targets))
for _, t := range g.Targets {
tg.Targets = append(tg.Targets, model.LabelSet{
model.AddressLabel: model.LabelValue(t),
})
}
tg.Labels = g.Labels
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (tg Group) MarshalJSON() ([]byte, error) {
g := &struct {
Targets []string `json:"targets"`
Labels model.LabelSet `json:"labels,omitempty"`
}{
Targets: make([]string, 0, len(tg.Targets)),
Labels: tg.Labels,
}
for _, t := range tg.Targets {
g.Targets = append(g.Targets, string(t[model.AddressLabel]))
}
return json.Marshal(g)
}

View File

@@ -0,0 +1,50 @@
// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar
import "github.com/prometheus/prometheus/model/labels"
// The combined length of the label names and values of an Exemplar's LabelSet MUST NOT exceed 128 UTF-8 characters
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#exemplars
const ExemplarMaxLabelSetLength = 128
// Exemplar is additional information associated with a time series.
type Exemplar struct {
Labels labels.Labels `json:"labels"`
Value float64 `json:"value"`
Ts int64 `json:"timestamp"`
HasTs bool
}
type QueryResult struct {
SeriesLabels labels.Labels `json:"seriesLabels"`
Exemplars []Exemplar `json:"exemplars"`
}
// Equals compares if the exemplar e is the same as e2. Note that if HasTs is false for
// both exemplars then the timestamps will be ignored for the comparison. This can come up
// when an exemplar is exported without it's own timestamp, in which case the scrape timestamp
// is assigned to the Ts field. However we still want to treat the same exemplar, scraped without
// an exported timestamp, as a duplicate of itself for each subsequent scrape.
func (e Exemplar) Equals(e2 Exemplar) bool {
if !labels.Equal(e.Labels, e2.Labels) {
return false
}
if (e.HasTs || e2.HasTs) && e.Ts != e2.Ts {
return false
}
return e.Value == e2.Value
}

View File

@@ -0,0 +1,904 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package histogram
import (
"fmt"
"strings"
)
// FloatHistogram is similar to Histogram but uses float64 for all
// counts. Additionally, bucket counts are absolute and not deltas.
//
// A FloatHistogram is needed by PromQL to handle operations that might result
// in fractional counts. Since the counts in a histogram are unlikely to be too
// large to be represented precisely by a float64, a FloatHistogram can also be
// used to represent a histogram with integer counts and thus serves as a more
// generalized representation.
type FloatHistogram struct {
// Counter reset information.
CounterResetHint CounterResetHint
// Currently valid schema numbers are -4 <= n <= 8. They are all for
// base-2 bucket schemas, where 1 is a bucket boundary in each case, and
// then each power of two is divided into 2^n logarithmic buckets. Or
// in other words, each bucket boundary is the previous boundary times
// 2^(2^-n).
Schema int32
// Width of the zero bucket.
ZeroThreshold float64
// Observations falling into the zero bucket. Must be zero or positive.
ZeroCount float64
// Total number of observations. Must be zero or positive.
Count float64
// Sum of observations. This is also used as the stale marker.
Sum float64
// Spans for positive and negative buckets (see Span below).
PositiveSpans, NegativeSpans []Span
// Observation counts in buckets. Each represents an absolute count and
// must be zero or positive.
PositiveBuckets, NegativeBuckets []float64
}
// Copy returns a deep copy of the Histogram.
func (h *FloatHistogram) Copy() *FloatHistogram {
c := *h
if h.PositiveSpans != nil {
c.PositiveSpans = make([]Span, len(h.PositiveSpans))
copy(c.PositiveSpans, h.PositiveSpans)
}
if h.NegativeSpans != nil {
c.NegativeSpans = make([]Span, len(h.NegativeSpans))
copy(c.NegativeSpans, h.NegativeSpans)
}
if h.PositiveBuckets != nil {
c.PositiveBuckets = make([]float64, len(h.PositiveBuckets))
copy(c.PositiveBuckets, h.PositiveBuckets)
}
if h.NegativeBuckets != nil {
c.NegativeBuckets = make([]float64, len(h.NegativeBuckets))
copy(c.NegativeBuckets, h.NegativeBuckets)
}
return &c
}
// CopyToSchema works like Copy, but the returned deep copy has the provided
// target schema, which must be ≤ the original schema (i.e. it must have a lower
// resolution).
func (h *FloatHistogram) CopyToSchema(targetSchema int32) *FloatHistogram {
if targetSchema == h.Schema {
// Fast path.
return h.Copy()
}
if targetSchema > h.Schema {
panic(fmt.Errorf("cannot copy from schema %d to %d", h.Schema, targetSchema))
}
c := FloatHistogram{
Schema: targetSchema,
ZeroThreshold: h.ZeroThreshold,
ZeroCount: h.ZeroCount,
Count: h.Count,
Sum: h.Sum,
}
// TODO(beorn7): This is a straight-forward implementation using merging
// iterators for the original buckets and then adding one merged bucket
// after another to the newly created FloatHistogram. It's well possible
// that a more involved implementation performs much better, which we
// could do if this code path turns out to be performance-critical.
var iInSpan, index int32
for iSpan, iBucket, it := -1, -1, h.floatBucketIterator(true, 0, targetSchema); it.Next(); {
b := it.At()
c.PositiveSpans, c.PositiveBuckets, iSpan, iBucket, iInSpan = addBucket(
b, c.PositiveSpans, c.PositiveBuckets, iSpan, iBucket, iInSpan, index,
)
index = b.Index
}
for iSpan, iBucket, it := -1, -1, h.floatBucketIterator(false, 0, targetSchema); it.Next(); {
b := it.At()
c.NegativeSpans, c.NegativeBuckets, iSpan, iBucket, iInSpan = addBucket(
b, c.NegativeSpans, c.NegativeBuckets, iSpan, iBucket, iInSpan, index,
)
index = b.Index
}
return &c
}
// String returns a string representation of the Histogram.
func (h *FloatHistogram) String() string {
var sb strings.Builder
fmt.Fprintf(&sb, "{count:%g, sum:%g", h.Count, h.Sum)
var nBuckets []Bucket[float64]
for it := h.NegativeBucketIterator(); it.Next(); {
bucket := it.At()
if bucket.Count != 0 {
nBuckets = append(nBuckets, it.At())
}
}
for i := len(nBuckets) - 1; i >= 0; i-- {
fmt.Fprintf(&sb, ", %s", nBuckets[i].String())
}
if h.ZeroCount != 0 {
fmt.Fprintf(&sb, ", %s", h.ZeroBucket().String())
}
for it := h.PositiveBucketIterator(); it.Next(); {
bucket := it.At()
if bucket.Count != 0 {
fmt.Fprintf(&sb, ", %s", bucket.String())
}
}
sb.WriteRune('}')
return sb.String()
}
// ZeroBucket returns the zero bucket.
func (h *FloatHistogram) ZeroBucket() Bucket[float64] {
return Bucket[float64]{
Lower: -h.ZeroThreshold,
Upper: h.ZeroThreshold,
LowerInclusive: true,
UpperInclusive: true,
Count: h.ZeroCount,
}
}
// Scale scales the FloatHistogram by the provided factor, i.e. it scales all
// bucket counts including the zero bucket and the count and the sum of
// observations. The bucket layout stays the same. This method changes the
// receiving histogram directly (rather than acting on a copy). It returns a
// pointer to the receiving histogram for convenience.
func (h *FloatHistogram) Scale(factor float64) *FloatHistogram {
h.ZeroCount *= factor
h.Count *= factor
h.Sum *= factor
for i := range h.PositiveBuckets {
h.PositiveBuckets[i] *= factor
}
for i := range h.NegativeBuckets {
h.NegativeBuckets[i] *= factor
}
return h
}
// Add adds the provided other histogram to the receiving histogram. Count, Sum,
// and buckets from the other histogram are added to the corresponding
// components of the receiving histogram. Buckets in the other histogram that do
// not exist in the receiving histogram are inserted into the latter. The
// resulting histogram might have buckets with a population of zero or directly
// adjacent spans (offset=0). To normalize those, call the Compact method.
//
// The method reconciles differences in the zero threshold and in the schema,
// but the schema of the other histogram must be ≥ the schema of the receiving
// histogram (i.e. must have an equal or higher resolution). This means that the
// schema of the receiving histogram won't change. Its zero threshold, however,
// will change if needed. The other histogram will not be modified in any case.
//
// This method returns a pointer to the receiving histogram for convenience.
func (h *FloatHistogram) Add(other *FloatHistogram) *FloatHistogram {
otherZeroCount := h.reconcileZeroBuckets(other)
h.ZeroCount += otherZeroCount
h.Count += other.Count
h.Sum += other.Sum
// TODO(beorn7): If needed, this can be optimized by inspecting the
// spans in other and create missing buckets in h in batches.
var iInSpan, index int32
for iSpan, iBucket, it := -1, -1, other.floatBucketIterator(true, h.ZeroThreshold, h.Schema); it.Next(); {
b := it.At()
h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan = addBucket(
b, h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan, index,
)
index = b.Index
}
for iSpan, iBucket, it := -1, -1, other.floatBucketIterator(false, h.ZeroThreshold, h.Schema); it.Next(); {
b := it.At()
h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan = addBucket(
b, h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan, index,
)
index = b.Index
}
return h
}
// Sub works like Add but subtracts the other histogram.
func (h *FloatHistogram) Sub(other *FloatHistogram) *FloatHistogram {
otherZeroCount := h.reconcileZeroBuckets(other)
h.ZeroCount -= otherZeroCount
h.Count -= other.Count
h.Sum -= other.Sum
// TODO(beorn7): If needed, this can be optimized by inspecting the
// spans in other and create missing buckets in h in batches.
var iInSpan, index int32
for iSpan, iBucket, it := -1, -1, other.floatBucketIterator(true, h.ZeroThreshold, h.Schema); it.Next(); {
b := it.At()
b.Count *= -1
h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan = addBucket(
b, h.PositiveSpans, h.PositiveBuckets, iSpan, iBucket, iInSpan, index,
)
index = b.Index
}
for iSpan, iBucket, it := -1, -1, other.floatBucketIterator(false, h.ZeroThreshold, h.Schema); it.Next(); {
b := it.At()
b.Count *= -1
h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan = addBucket(
b, h.NegativeSpans, h.NegativeBuckets, iSpan, iBucket, iInSpan, index,
)
index = b.Index
}
return h
}
// Equals returns true if the given float histogram matches exactly.
// Exact match is when there are no new buckets (even empty) and no missing buckets,
// and all the bucket values match. Spans can have different empty length spans in between,
// but they must represent the same bucket layout to match.
func (h *FloatHistogram) Equals(h2 *FloatHistogram) bool {
if h2 == nil {
return false
}
if h.Schema != h2.Schema || h.ZeroThreshold != h2.ZeroThreshold ||
h.ZeroCount != h2.ZeroCount || h.Count != h2.Count || h.Sum != h2.Sum {
return false
}
if !spansMatch(h.PositiveSpans, h2.PositiveSpans) {
return false
}
if !spansMatch(h.NegativeSpans, h2.NegativeSpans) {
return false
}
if !bucketsMatch(h.PositiveBuckets, h2.PositiveBuckets) {
return false
}
if !bucketsMatch(h.NegativeBuckets, h2.NegativeBuckets) {
return false
}
return true
}
// addBucket takes the "coordinates" of the last bucket that was handled and
// adds the provided bucket after it. If a corresponding bucket exists, the
// count is added. If not, the bucket is inserted. The updated slices and the
// coordinates of the inserted or added-to bucket are returned.
func addBucket(
b Bucket[float64],
spans []Span, buckets []float64,
iSpan, iBucket int,
iInSpan, index int32,
) (
newSpans []Span, newBuckets []float64,
newISpan, newIBucket int, newIInSpan int32,
) {
if iSpan == -1 {
// First add, check if it is before all spans.
if len(spans) == 0 || spans[0].Offset > b.Index {
// Add bucket before all others.
buckets = append(buckets, 0)
copy(buckets[1:], buckets)
buckets[0] = b.Count
if len(spans) > 0 && spans[0].Offset == b.Index+1 {
spans[0].Length++
spans[0].Offset--
return spans, buckets, 0, 0, 0
}
spans = append(spans, Span{})
copy(spans[1:], spans)
spans[0] = Span{Offset: b.Index, Length: 1}
if len(spans) > 1 {
// Convert the absolute offset in the formerly
// first span to a relative offset.
spans[1].Offset -= b.Index + 1
}
return spans, buckets, 0, 0, 0
}
if spans[0].Offset == b.Index {
// Just add to first bucket.
buckets[0] += b.Count
return spans, buckets, 0, 0, 0
}
// We are behind the first bucket, so set everything to the
// first bucket and continue normally.
iSpan, iBucket, iInSpan = 0, 0, 0
index = spans[0].Offset
}
deltaIndex := b.Index - index
for {
remainingInSpan := int32(spans[iSpan].Length) - iInSpan
if deltaIndex < remainingInSpan {
// Bucket is in current span.
iBucket += int(deltaIndex)
iInSpan += deltaIndex
buckets[iBucket] += b.Count
return spans, buckets, iSpan, iBucket, iInSpan
}
deltaIndex -= remainingInSpan
iBucket += int(remainingInSpan)
iSpan++
if iSpan == len(spans) || deltaIndex < spans[iSpan].Offset {
// Bucket is in gap behind previous span (or there are no further spans).
buckets = append(buckets, 0)
copy(buckets[iBucket+1:], buckets[iBucket:])
buckets[iBucket] = b.Count
if deltaIndex == 0 {
// Directly after previous span, extend previous span.
if iSpan < len(spans) {
spans[iSpan].Offset--
}
iSpan--
iInSpan = int32(spans[iSpan].Length)
spans[iSpan].Length++
return spans, buckets, iSpan, iBucket, iInSpan
}
if iSpan < len(spans) && deltaIndex == spans[iSpan].Offset-1 {
// Directly before next span, extend next span.
iInSpan = 0
spans[iSpan].Offset--
spans[iSpan].Length++
return spans, buckets, iSpan, iBucket, iInSpan
}
// No next span, or next span is not directly adjacent to new bucket.
// Add new span.
iInSpan = 0
if iSpan < len(spans) {
spans[iSpan].Offset -= deltaIndex + 1
}
spans = append(spans, Span{})
copy(spans[iSpan+1:], spans[iSpan:])
spans[iSpan] = Span{Length: 1, Offset: deltaIndex}
return spans, buckets, iSpan, iBucket, iInSpan
}
// Try start of next span.
deltaIndex -= spans[iSpan].Offset
iInSpan = 0
}
}
// Compact eliminates empty buckets at the beginning and end of each span, then
// merges spans that are consecutive or at most maxEmptyBuckets apart, and
// finally splits spans that contain more consecutive empty buckets than
// maxEmptyBuckets. (The actual implementation might do something more efficient
// but with the same result.) The compaction happens "in place" in the
// receiving histogram, but a pointer to it is returned for convenience.
//
// The ideal value for maxEmptyBuckets depends on circumstances. The motivation
// to set maxEmptyBuckets > 0 is the assumption that is is less overhead to
// represent very few empty buckets explicitly within one span than cutting the
// one span into two to treat the empty buckets as a gap between the two spans,
// both in terms of storage requirement as well as in terms of encoding and
// decoding effort. However, the tradeoffs are subtle. For one, they are
// different in the exposition format vs. in a TSDB chunk vs. for the in-memory
// representation as Go types. In the TSDB, as an additional aspects, the span
// layout is only stored once per chunk, while many histograms with that same
// chunk layout are then only stored with their buckets (so that even a single
// empty bucket will be stored many times).
//
// For the Go types, an additional Span takes 8 bytes. Similarly, an additional
// bucket takes 8 bytes. Therefore, with a single separating empty bucket, both
// options have the same storage requirement, but the single-span solution is
// easier to iterate through. Still, the safest bet is to use maxEmptyBuckets==0
// and only use a larger number if you know what you are doing.
func (h *FloatHistogram) Compact(maxEmptyBuckets int) *FloatHistogram {
h.PositiveBuckets, h.PositiveSpans = compactBuckets(
h.PositiveBuckets, h.PositiveSpans, maxEmptyBuckets, false,
)
h.NegativeBuckets, h.NegativeSpans = compactBuckets(
h.NegativeBuckets, h.NegativeSpans, maxEmptyBuckets, false,
)
return h
}
// DetectReset returns true if the receiving histogram is missing any buckets
// that have a non-zero population in the provided previous histogram. It also
// returns true if any count (in any bucket, in the zero count, or in the count
// of observations, but NOT the sum of observations) is smaller in the receiving
// histogram compared to the previous histogram. Otherwise, it returns false.
//
// Special behavior in case the Schema or the ZeroThreshold are not the same in
// both histograms:
//
// - A decrease of the ZeroThreshold or an increase of the Schema (i.e. an
// increase of resolution) can only happen together with a reset. Thus, the
// method returns true in either case.
//
// - Upon an increase of the ZeroThreshold, the buckets in the previous
// histogram that fall within the new ZeroThreshold are added to the ZeroCount
// of the previous histogram (without mutating the provided previous
// histogram). The scenario that a populated bucket of the previous histogram
// is partially within, partially outside of the new ZeroThreshold, can only
// happen together with a counter reset and therefore shortcuts to returning
// true.
//
// - Upon a decrease of the Schema, the buckets of the previous histogram are
// merged so that they match the new, lower-resolution schema (again without
// mutating the provided previous histogram).
//
// Note that this kind of reset detection is quite expensive. Ideally, resets
// are detected at ingest time and stored in the TSDB, so that the reset
// information can be read directly from there rather than be detected each time
// again.
func (h *FloatHistogram) DetectReset(previous *FloatHistogram) bool {
if h.Count < previous.Count {
return true
}
if h.Schema > previous.Schema {
return true
}
if h.ZeroThreshold < previous.ZeroThreshold {
// ZeroThreshold decreased.
return true
}
previousZeroCount, newThreshold := previous.zeroCountForLargerThreshold(h.ZeroThreshold)
if newThreshold != h.ZeroThreshold {
// ZeroThreshold is within a populated bucket in previous
// histogram.
return true
}
if h.ZeroCount < previousZeroCount {
return true
}
currIt := h.floatBucketIterator(true, h.ZeroThreshold, h.Schema)
prevIt := previous.floatBucketIterator(true, h.ZeroThreshold, h.Schema)
if detectReset(currIt, prevIt) {
return true
}
currIt = h.floatBucketIterator(false, h.ZeroThreshold, h.Schema)
prevIt = previous.floatBucketIterator(false, h.ZeroThreshold, h.Schema)
return detectReset(currIt, prevIt)
}
func detectReset(currIt, prevIt BucketIterator[float64]) bool {
if !prevIt.Next() {
return false // If no buckets in previous histogram, nothing can be reset.
}
prevBucket := prevIt.At()
if !currIt.Next() {
// No bucket in current, but at least one in previous
// histogram. Check if any of those are non-zero, in which case
// this is a reset.
for {
if prevBucket.Count != 0 {
return true
}
if !prevIt.Next() {
return false
}
}
}
currBucket := currIt.At()
for {
// Forward currIt until we find the bucket corresponding to prevBucket.
for currBucket.Index < prevBucket.Index {
if !currIt.Next() {
// Reached end of currIt early, therefore
// previous histogram has a bucket that the
// current one does not have. Unlass all
// remaining buckets in the previous histogram
// are unpopulated, this is a reset.
for {
if prevBucket.Count != 0 {
return true
}
if !prevIt.Next() {
return false
}
}
}
currBucket = currIt.At()
}
if currBucket.Index > prevBucket.Index {
// Previous histogram has a bucket the current one does
// not have. If it's populated, it's a reset.
if prevBucket.Count != 0 {
return true
}
} else {
// We have reached corresponding buckets in both iterators.
// We can finally compare the counts.
if currBucket.Count < prevBucket.Count {
return true
}
}
if !prevIt.Next() {
// Reached end of prevIt without finding offending buckets.
return false
}
prevBucket = prevIt.At()
}
}
// PositiveBucketIterator returns a BucketIterator to iterate over all positive
// buckets in ascending order (starting next to the zero bucket and going up).
func (h *FloatHistogram) PositiveBucketIterator() BucketIterator[float64] {
return h.floatBucketIterator(true, 0, h.Schema)
}
// NegativeBucketIterator returns a BucketIterator to iterate over all negative
// buckets in descending order (starting next to the zero bucket and going
// down).
func (h *FloatHistogram) NegativeBucketIterator() BucketIterator[float64] {
return h.floatBucketIterator(false, 0, h.Schema)
}
// PositiveReverseBucketIterator returns a BucketIterator to iterate over all
// positive buckets in descending order (starting at the highest bucket and
// going down towards the zero bucket).
func (h *FloatHistogram) PositiveReverseBucketIterator() BucketIterator[float64] {
return newReverseFloatBucketIterator(h.PositiveSpans, h.PositiveBuckets, h.Schema, true)
}
// NegativeReverseBucketIterator returns a BucketIterator to iterate over all
// negative buckets in ascending order (starting at the lowest bucket and going
// up towards the zero bucket).
func (h *FloatHistogram) NegativeReverseBucketIterator() BucketIterator[float64] {
return newReverseFloatBucketIterator(h.NegativeSpans, h.NegativeBuckets, h.Schema, false)
}
// AllBucketIterator returns a BucketIterator to iterate over all negative,
// zero, and positive buckets in ascending order (starting at the lowest bucket
// and going up). If the highest negative bucket or the lowest positive bucket
// overlap with the zero bucket, their upper or lower boundary, respectively, is
// set to the zero threshold.
func (h *FloatHistogram) AllBucketIterator() BucketIterator[float64] {
return &allFloatBucketIterator{
h: h,
negIter: h.NegativeReverseBucketIterator(),
posIter: h.PositiveBucketIterator(),
state: -1,
}
}
// zeroCountForLargerThreshold returns what the histogram's zero count would be
// if the ZeroThreshold had the provided larger (or equal) value. If the
// provided value is less than the histogram's ZeroThreshold, the method panics.
// If the largerThreshold ends up within a populated bucket of the histogram, it
// is adjusted upwards to the lower limit of that bucket (all in terms of
// absolute values) and that bucket's count is included in the returned
// count. The adjusted threshold is returned, too.
func (h *FloatHistogram) zeroCountForLargerThreshold(largerThreshold float64) (count, threshold float64) {
// Fast path.
if largerThreshold == h.ZeroThreshold {
return h.ZeroCount, largerThreshold
}
if largerThreshold < h.ZeroThreshold {
panic(fmt.Errorf("new threshold %f is less than old threshold %f", largerThreshold, h.ZeroThreshold))
}
outer:
for {
count = h.ZeroCount
i := h.PositiveBucketIterator()
for i.Next() {
b := i.At()
if b.Lower >= largerThreshold {
break
}
count += b.Count // Bucket to be merged into zero bucket.
if b.Upper > largerThreshold {
// New threshold ended up within a bucket. if it's
// populated, we need to adjust largerThreshold before
// we are done here.
if b.Count != 0 {
largerThreshold = b.Upper
}
break
}
}
i = h.NegativeBucketIterator()
for i.Next() {
b := i.At()
if b.Upper <= -largerThreshold {
break
}
count += b.Count // Bucket to be merged into zero bucket.
if b.Lower < -largerThreshold {
// New threshold ended up within a bucket. If
// it's populated, we need to adjust
// largerThreshold and have to redo the whole
// thing because the treatment of the positive
// buckets is invalid now.
if b.Count != 0 {
largerThreshold = -b.Lower
continue outer
}
break
}
}
return count, largerThreshold
}
}
// trimBucketsInZeroBucket removes all buckets that are within the zero
// bucket. It assumes that the zero threshold is at a bucket boundary and that
// the counts in the buckets to remove are already part of the zero count.
func (h *FloatHistogram) trimBucketsInZeroBucket() {
i := h.PositiveBucketIterator()
bucketsIdx := 0
for i.Next() {
b := i.At()
if b.Lower >= h.ZeroThreshold {
break
}
h.PositiveBuckets[bucketsIdx] = 0
bucketsIdx++
}
i = h.NegativeBucketIterator()
bucketsIdx = 0
for i.Next() {
b := i.At()
if b.Upper <= -h.ZeroThreshold {
break
}
h.NegativeBuckets[bucketsIdx] = 0
bucketsIdx++
}
// We are abusing Compact to trim the buckets set to zero
// above. Premature compacting could cause additional cost, but this
// code path is probably rarely used anyway.
h.Compact(0)
}
// reconcileZeroBuckets finds a zero bucket large enough to include the zero
// buckets of both histograms (the receiving histogram and the other histogram)
// with a zero threshold that is not within a populated bucket in either
// histogram. This method modifies the receiving histogram accourdingly, but
// leaves the other histogram as is. Instead, it returns the zero count the
// other histogram would have if it were modified.
func (h *FloatHistogram) reconcileZeroBuckets(other *FloatHistogram) float64 {
otherZeroCount := other.ZeroCount
otherZeroThreshold := other.ZeroThreshold
for otherZeroThreshold != h.ZeroThreshold {
if h.ZeroThreshold > otherZeroThreshold {
otherZeroCount, otherZeroThreshold = other.zeroCountForLargerThreshold(h.ZeroThreshold)
}
if otherZeroThreshold > h.ZeroThreshold {
h.ZeroCount, h.ZeroThreshold = h.zeroCountForLargerThreshold(otherZeroThreshold)
h.trimBucketsInZeroBucket()
}
}
return otherZeroCount
}
// floatBucketIterator is a low-level constructor for bucket iterators.
//
// If positive is true, the returned iterator iterates through the positive
// buckets, otherwise through the negative buckets.
//
// If absoluteStartValue is < the lowest absolute value of any upper bucket
// boundary, the iterator starts with the first bucket. Otherwise, it will skip
// all buckets with an absolute value of their upper boundary ≤
// absoluteStartValue.
//
// targetSchema must be ≤ the schema of FloatHistogram (and of course within the
// legal values for schemas in general). The buckets are merged to match the
// targetSchema prior to iterating (without mutating FloatHistogram).
func (h *FloatHistogram) floatBucketIterator(
positive bool, absoluteStartValue float64, targetSchema int32,
) *floatBucketIterator {
if targetSchema > h.Schema {
panic(fmt.Errorf("cannot merge from schema %d to %d", h.Schema, targetSchema))
}
i := &floatBucketIterator{
baseBucketIterator: baseBucketIterator[float64, float64]{
schema: h.Schema,
positive: positive,
},
targetSchema: targetSchema,
absoluteStartValue: absoluteStartValue,
}
if positive {
i.spans = h.PositiveSpans
i.buckets = h.PositiveBuckets
} else {
i.spans = h.NegativeSpans
i.buckets = h.NegativeBuckets
}
return i
}
// reverseFloatbucketiterator is a low-level constructor for reverse bucket iterators.
func newReverseFloatBucketIterator(
spans []Span, buckets []float64, schema int32, positive bool,
) *reverseFloatBucketIterator {
r := &reverseFloatBucketIterator{
baseBucketIterator: baseBucketIterator[float64, float64]{
schema: schema,
spans: spans,
buckets: buckets,
positive: positive,
},
}
r.spansIdx = len(r.spans) - 1
r.bucketsIdx = len(r.buckets) - 1
if r.spansIdx >= 0 {
r.idxInSpan = int32(r.spans[r.spansIdx].Length) - 1
}
r.currIdx = 0
for _, s := range r.spans {
r.currIdx += s.Offset + int32(s.Length)
}
return r
}
type floatBucketIterator struct {
baseBucketIterator[float64, float64]
targetSchema int32 // targetSchema is the schema to merge to and must be ≤ schema.
origIdx int32 // The bucket index within the original schema.
absoluteStartValue float64 // Never return buckets with an upper bound ≤ this value.
}
func (i *floatBucketIterator) Next() bool {
if i.spansIdx >= len(i.spans) {
return false
}
// Copy all of these into local variables so that we can forward to the
// next bucket and then roll back if needed.
origIdx, spansIdx, idxInSpan := i.origIdx, i.spansIdx, i.idxInSpan
span := i.spans[spansIdx]
firstPass := true
i.currCount = 0
mergeLoop: // Merge together all buckets from the original schema that fall into one bucket in the targetSchema.
for {
if i.bucketsIdx == 0 {
// Seed origIdx for the first bucket.
origIdx = span.Offset
} else {
origIdx++
}
for idxInSpan >= span.Length {
// We have exhausted the current span and have to find a new
// one. We even handle pathologic spans of length 0 here.
idxInSpan = 0
spansIdx++
if spansIdx >= len(i.spans) {
if firstPass {
return false
}
break mergeLoop
}
span = i.spans[spansIdx]
origIdx += span.Offset
}
currIdx := i.targetIdx(origIdx)
if firstPass {
i.currIdx = currIdx
firstPass = false
} else if currIdx != i.currIdx {
// Reached next bucket in targetSchema.
// Do not actually forward to the next bucket, but break out.
break mergeLoop
}
i.currCount += i.buckets[i.bucketsIdx]
idxInSpan++
i.bucketsIdx++
i.origIdx, i.spansIdx, i.idxInSpan = origIdx, spansIdx, idxInSpan
if i.schema == i.targetSchema {
// Don't need to test the next bucket for mergeability
// if we have no schema change anyway.
break mergeLoop
}
}
// Skip buckets before absoluteStartValue.
// TODO(beorn7): Maybe do something more efficient than this recursive call.
if getBound(i.currIdx, i.targetSchema) <= i.absoluteStartValue {
return i.Next()
}
return true
}
// targetIdx returns the bucket index within i.targetSchema for the given bucket
// index within i.schema.
func (i *floatBucketIterator) targetIdx(idx int32) int32 {
if i.schema == i.targetSchema {
// Fast path for the common case. The below would yield the same
// result, just with more effort.
return idx
}
return ((idx - 1) >> (i.schema - i.targetSchema)) + 1
}
type reverseFloatBucketIterator struct {
baseBucketIterator[float64, float64]
idxInSpan int32 // Changed from uint32 to allow negative values for exhaustion detection.
}
func (i *reverseFloatBucketIterator) Next() bool {
i.currIdx--
if i.bucketsIdx < 0 {
return false
}
for i.idxInSpan < 0 {
// We have exhausted the current span and have to find a new
// one. We'll even handle pathologic spans of length 0.
i.spansIdx--
i.idxInSpan = int32(i.spans[i.spansIdx].Length) - 1
i.currIdx -= i.spans[i.spansIdx+1].Offset
}
i.currCount = i.buckets[i.bucketsIdx]
i.bucketsIdx--
i.idxInSpan--
return true
}
type allFloatBucketIterator struct {
h *FloatHistogram
negIter, posIter BucketIterator[float64]
// -1 means we are iterating negative buckets.
// 0 means it is time for the zero bucket.
// 1 means we are iterating positive buckets.
// Anything else means iteration is over.
state int8
currBucket Bucket[float64]
}
func (i *allFloatBucketIterator) Next() bool {
switch i.state {
case -1:
if i.negIter.Next() {
i.currBucket = i.negIter.At()
if i.currBucket.Upper > -i.h.ZeroThreshold {
i.currBucket.Upper = -i.h.ZeroThreshold
}
return true
}
i.state = 0
return i.Next()
case 0:
i.state = 1
if i.h.ZeroCount > 0 {
i.currBucket = Bucket[float64]{
Lower: -i.h.ZeroThreshold,
Upper: i.h.ZeroThreshold,
LowerInclusive: true,
UpperInclusive: true,
Count: i.h.ZeroCount,
// Index is irrelevant for the zero bucket.
}
return true
}
return i.Next()
case 1:
if i.posIter.Next() {
i.currBucket = i.posIter.At()
if i.currBucket.Lower < i.h.ZeroThreshold {
i.currBucket.Lower = i.h.ZeroThreshold
}
return true
}
i.state = 42
return false
}
return false
}
func (i *allFloatBucketIterator) At() Bucket[float64] {
return i.currBucket
}

View File

@@ -0,0 +1,548 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package histogram
import (
"fmt"
"math"
"strings"
)
// BucketCount is a type constraint for the count in a bucket, which can be
// float64 (for type FloatHistogram) or uint64 (for type Histogram).
type BucketCount interface {
float64 | uint64
}
// InternalBucketCount is used internally by Histogram and FloatHistogram. The
// difference to the BucketCount above is that Histogram internally uses deltas
// between buckets rather than absolute counts (while FloatHistogram uses
// absolute counts directly). Go type parameters don't allow type
// specialization. Therefore, where special treatment of deltas between buckets
// vs. absolute counts is important, this information has to be provided as a
// separate boolean parameter "deltaBuckets"
type InternalBucketCount interface {
float64 | int64
}
// Bucket represents a bucket with lower and upper limit and the absolute count
// of samples in the bucket. It also specifies if each limit is inclusive or
// not. (Mathematically, inclusive limits create a closed interval, and
// non-inclusive limits an open interval.)
//
// To represent cumulative buckets, Lower is set to -Inf, and the Count is then
// cumulative (including the counts of all buckets for smaller values).
type Bucket[BC BucketCount] struct {
Lower, Upper float64
LowerInclusive, UpperInclusive bool
Count BC
// Index within schema. To easily compare buckets that share the same
// schema and sign (positive or negative). Irrelevant for the zero bucket.
Index int32
}
// String returns a string representation of a Bucket, using the usual
// mathematical notation of '['/']' for inclusive bounds and '('/')' for
// non-inclusive bounds.
func (b Bucket[BC]) String() string {
var sb strings.Builder
if b.LowerInclusive {
sb.WriteRune('[')
} else {
sb.WriteRune('(')
}
fmt.Fprintf(&sb, "%g,%g", b.Lower, b.Upper)
if b.UpperInclusive {
sb.WriteRune(']')
} else {
sb.WriteRune(')')
}
fmt.Fprintf(&sb, ":%v", b.Count)
return sb.String()
}
// BucketIterator iterates over the buckets of a Histogram, returning decoded
// buckets.
type BucketIterator[BC BucketCount] interface {
// Next advances the iterator by one.
Next() bool
// At returns the current bucket.
At() Bucket[BC]
}
// baseBucketIterator provides a struct that is shared by most BucketIterator
// implementations, together with an implementation of the At method. This
// iterator can be embedded in full implementations of BucketIterator to save on
// code replication.
type baseBucketIterator[BC BucketCount, IBC InternalBucketCount] struct {
schema int32
spans []Span
buckets []IBC
positive bool // Whether this is for positive buckets.
spansIdx int // Current span within spans slice.
idxInSpan uint32 // Index in the current span. 0 <= idxInSpan < span.Length.
bucketsIdx int // Current bucket within buckets slice.
currCount IBC // Count in the current bucket.
currIdx int32 // The actual bucket index.
}
func (b baseBucketIterator[BC, IBC]) At() Bucket[BC] {
bucket := Bucket[BC]{
Count: BC(b.currCount),
Index: b.currIdx,
}
if b.positive {
bucket.Upper = getBound(b.currIdx, b.schema)
bucket.Lower = getBound(b.currIdx-1, b.schema)
} else {
bucket.Lower = -getBound(b.currIdx, b.schema)
bucket.Upper = -getBound(b.currIdx-1, b.schema)
}
bucket.LowerInclusive = bucket.Lower < 0
bucket.UpperInclusive = bucket.Upper > 0
return bucket
}
// compactBuckets is a generic function used by both Histogram.Compact and
// FloatHistogram.Compact. Set deltaBuckets to true if the provided buckets are
// deltas. Set it to false if the buckets contain absolute counts.
func compactBuckets[IBC InternalBucketCount](buckets []IBC, spans []Span, maxEmptyBuckets int, deltaBuckets bool) ([]IBC, []Span) {
// Fast path: If there are no empty buckets AND no offset in any span is
// <= maxEmptyBuckets AND no span has length 0, there is nothing to do and we can return
// immediately. We check that first because it's cheap and presumably
// common.
nothingToDo := true
var currentBucketAbsolute IBC
for _, bucket := range buckets {
if deltaBuckets {
currentBucketAbsolute += bucket
} else {
currentBucketAbsolute = bucket
}
if currentBucketAbsolute == 0 {
nothingToDo = false
break
}
}
if nothingToDo {
for _, span := range spans {
if int(span.Offset) <= maxEmptyBuckets || span.Length == 0 {
nothingToDo = false
break
}
}
if nothingToDo {
return buckets, spans
}
}
var iBucket, iSpan int
var posInSpan uint32
currentBucketAbsolute = 0
// Helper function.
emptyBucketsHere := func() int {
i := 0
abs := currentBucketAbsolute
for uint32(i)+posInSpan < spans[iSpan].Length && abs == 0 {
i++
if i+iBucket >= len(buckets) {
break
}
abs = buckets[i+iBucket]
}
return i
}
// Merge spans with zero-offset to avoid special cases later.
if len(spans) > 1 {
for i, span := range spans[1:] {
if span.Offset == 0 {
spans[iSpan].Length += span.Length
continue
}
iSpan++
if i+1 != iSpan {
spans[iSpan] = span
}
}
spans = spans[:iSpan+1]
iSpan = 0
}
// Merge spans with zero-length to avoid special cases later.
for i, span := range spans {
if span.Length == 0 {
if i+1 < len(spans) {
spans[i+1].Offset += span.Offset
}
continue
}
if i != iSpan {
spans[iSpan] = span
}
iSpan++
}
spans = spans[:iSpan]
iSpan = 0
// Cut out empty buckets from start and end of spans, no matter
// what. Also cut out empty buckets from the middle of a span but only
// if there are more than maxEmptyBuckets consecutive empty buckets.
for iBucket < len(buckets) {
if deltaBuckets {
currentBucketAbsolute += buckets[iBucket]
} else {
currentBucketAbsolute = buckets[iBucket]
}
if nEmpty := emptyBucketsHere(); nEmpty > 0 {
if posInSpan > 0 &&
nEmpty < int(spans[iSpan].Length-posInSpan) &&
nEmpty <= maxEmptyBuckets {
// The empty buckets are in the middle of a
// span, and there are few enough to not bother.
// Just fast-forward.
iBucket += nEmpty
if deltaBuckets {
currentBucketAbsolute = 0
}
posInSpan += uint32(nEmpty)
continue
}
// In all other cases, we cut out the empty buckets.
if deltaBuckets && iBucket+nEmpty < len(buckets) {
currentBucketAbsolute = -buckets[iBucket]
buckets[iBucket+nEmpty] += buckets[iBucket]
}
buckets = append(buckets[:iBucket], buckets[iBucket+nEmpty:]...)
if posInSpan == 0 {
// Start of span.
if nEmpty == int(spans[iSpan].Length) {
// The whole span is empty.
offset := spans[iSpan].Offset
spans = append(spans[:iSpan], spans[iSpan+1:]...)
if len(spans) > iSpan {
spans[iSpan].Offset += offset + int32(nEmpty)
}
continue
}
spans[iSpan].Length -= uint32(nEmpty)
spans[iSpan].Offset += int32(nEmpty)
continue
}
// It's in the middle or in the end of the span.
// Split the current span.
newSpan := Span{
Offset: int32(nEmpty),
Length: spans[iSpan].Length - posInSpan - uint32(nEmpty),
}
spans[iSpan].Length = posInSpan
// In any case, we have to split to the next span.
iSpan++
posInSpan = 0
if newSpan.Length == 0 {
// The span is empty, so we were already at the end of a span.
// We don't have to insert the new span, just adjust the next
// span's offset, if there is one.
if iSpan < len(spans) {
spans[iSpan].Offset += int32(nEmpty)
}
continue
}
// Insert the new span.
spans = append(spans, Span{})
if iSpan+1 < len(spans) {
copy(spans[iSpan+1:], spans[iSpan:])
}
spans[iSpan] = newSpan
continue
}
iBucket++
posInSpan++
if posInSpan >= spans[iSpan].Length {
posInSpan = 0
iSpan++
}
}
if maxEmptyBuckets == 0 || len(buckets) == 0 {
return buckets, spans
}
// Finally, check if any offsets between spans are small enough to merge
// the spans.
iBucket = int(spans[0].Length)
if deltaBuckets {
currentBucketAbsolute = 0
for _, bucket := range buckets[:iBucket] {
currentBucketAbsolute += bucket
}
}
iSpan = 1
for iSpan < len(spans) {
if int(spans[iSpan].Offset) > maxEmptyBuckets {
l := int(spans[iSpan].Length)
if deltaBuckets {
for _, bucket := range buckets[iBucket : iBucket+l] {
currentBucketAbsolute += bucket
}
}
iBucket += l
iSpan++
continue
}
// Merge span with previous one and insert empty buckets.
offset := int(spans[iSpan].Offset)
spans[iSpan-1].Length += uint32(offset) + spans[iSpan].Length
spans = append(spans[:iSpan], spans[iSpan+1:]...)
newBuckets := make([]IBC, len(buckets)+offset)
copy(newBuckets, buckets[:iBucket])
copy(newBuckets[iBucket+offset:], buckets[iBucket:])
if deltaBuckets {
newBuckets[iBucket] = -currentBucketAbsolute
newBuckets[iBucket+offset] += currentBucketAbsolute
}
iBucket += offset
buckets = newBuckets
currentBucketAbsolute = buckets[iBucket]
// Note that with many merges, it would be more efficient to
// first record all the chunks of empty buckets to insert and
// then do it in one go through all the buckets.
}
return buckets, spans
}
func bucketsMatch[IBC InternalBucketCount](b1, b2 []IBC) bool {
if len(b1) != len(b2) {
return false
}
for i, b := range b1 {
if b != b2[i] {
return false
}
}
return true
}
func getBound(idx, schema int32) float64 {
// Here a bit of context about the behavior for the last bucket counting
// regular numbers (called simply "last bucket" below) and the bucket
// counting observations of ±Inf (called "inf bucket" below, with an idx
// one higher than that of the "last bucket"):
//
// If we apply the usual formula to the last bucket, its upper bound
// would be calculated as +Inf. The reason is that the max possible
// regular float64 number (math.MaxFloat64) doesn't coincide with one of
// the calculated bucket boundaries. So the calculated boundary has to
// be larger than math.MaxFloat64, and the only float64 larger than
// math.MaxFloat64 is +Inf. However, we want to count actual
// observations of ±Inf in the inf bucket. Therefore, we have to treat
// the upper bound of the last bucket specially and set it to
// math.MaxFloat64. (The upper bound of the inf bucket, with its idx
// being one higher than that of the last bucket, naturally comes out as
// +Inf by the usual formula. So that's fine.)
//
// math.MaxFloat64 has a frac of 0.9999999999999999 and an exp of
// 1024. If there were a float64 number following math.MaxFloat64, it
// would have a frac of 1.0 and an exp of 1024, or equivalently a frac
// of 0.5 and an exp of 1025. However, since frac must be smaller than
// 1, and exp must be smaller than 1025, either representation overflows
// a float64. (Which, in turn, is the reason that math.MaxFloat64 is the
// largest possible float64. Q.E.D.) However, the formula for
// calculating the upper bound from the idx and schema of the last
// bucket results in precisely that. It is either frac=1.0 & exp=1024
// (for schema < 0) or frac=0.5 & exp=1025 (for schema >=0). (This is,
// by the way, a power of two where the exponent itself is a power of
// two, 2¹⁰ in fact, which coinicides with a bucket boundary in all
// schemas.) So these are the special cases we have to catch below.
if schema < 0 {
exp := int(idx) << -schema
if exp == 1024 {
// This is the last bucket before the overflow bucket
// (for ±Inf observations). Return math.MaxFloat64 as
// explained above.
return math.MaxFloat64
}
return math.Ldexp(1, exp)
}
fracIdx := idx & ((1 << schema) - 1)
frac := exponentialBounds[schema][fracIdx]
exp := (int(idx) >> schema) + 1
if frac == 0.5 && exp == 1025 {
// This is the last bucket before the overflow bucket (for ±Inf
// observations). Return math.MaxFloat64 as explained above.
return math.MaxFloat64
}
return math.Ldexp(frac, exp)
}
// exponentialBounds is a precalculated table of bucket bounds in the interval
// [0.5,1) in schema 0 to 8.
var exponentialBounds = [][]float64{
// Schema "0":
{0.5},
// Schema 1:
{0.5, 0.7071067811865475},
// Schema 2:
{0.5, 0.5946035575013605, 0.7071067811865475, 0.8408964152537144},
// Schema 3:
{
0.5, 0.5452538663326288, 0.5946035575013605, 0.6484197773255048,
0.7071067811865475, 0.7711054127039704, 0.8408964152537144, 0.9170040432046711,
},
// Schema 4:
{
0.5, 0.5221368912137069, 0.5452538663326288, 0.5693943173783458,
0.5946035575013605, 0.620928906036742, 0.6484197773255048, 0.6771277734684463,
0.7071067811865475, 0.7384130729697496, 0.7711054127039704, 0.805245165974627,
0.8408964152537144, 0.8781260801866495, 0.9170040432046711, 0.9576032806985735,
},
// Schema 5:
{
0.5, 0.5109485743270583, 0.5221368912137069, 0.5335702003384117,
0.5452538663326288, 0.5571933712979462, 0.5693943173783458, 0.5818624293887887,
0.5946035575013605, 0.6076236799902344, 0.620928906036742, 0.6345254785958666,
0.6484197773255048, 0.6626183215798706, 0.6771277734684463, 0.6919549409819159,
0.7071067811865475, 0.7225904034885232, 0.7384130729697496, 0.7545822137967112,
0.7711054127039704, 0.7879904225539431, 0.805245165974627, 0.8228777390769823,
0.8408964152537144, 0.8593096490612387, 0.8781260801866495, 0.8973545375015533,
0.9170040432046711, 0.9370838170551498, 0.9576032806985735, 0.9785720620876999,
},
// Schema 6:
{
0.5, 0.5054446430258502, 0.5109485743270583, 0.5165124395106142,
0.5221368912137069, 0.5278225891802786, 0.5335702003384117, 0.5393803988785598,
0.5452538663326288, 0.5511912916539204, 0.5571933712979462, 0.5632608093041209,
0.5693943173783458, 0.5755946149764913, 0.5818624293887887, 0.5881984958251406,
0.5946035575013605, 0.6010783657263515, 0.6076236799902344, 0.6142402680534349,
0.620928906036742, 0.6276903785123455, 0.6345254785958666, 0.6414350080393891,
0.6484197773255048, 0.6554806057623822, 0.6626183215798706, 0.6698337620266515,
0.6771277734684463, 0.6845012114872953, 0.6919549409819159, 0.6994898362691555,
0.7071067811865475, 0.7148066691959849, 0.7225904034885232, 0.7304588970903234,
0.7384130729697496, 0.7464538641456323, 0.7545822137967112, 0.762799075372269,
0.7711054127039704, 0.7795022001189185, 0.7879904225539431, 0.7965710756711334,
0.805245165974627, 0.8140137109286738, 0.8228777390769823, 0.8318382901633681,
0.8408964152537144, 0.8500531768592616, 0.8593096490612387, 0.8686669176368529,
0.8781260801866495, 0.8876882462632604, 0.8973545375015533, 0.9071260877501991,
0.9170040432046711, 0.9269895625416926, 0.9370838170551498, 0.9472879907934827,
0.9576032806985735, 0.9680308967461471, 0.9785720620876999, 0.9892280131939752,
},
// Schema 7:
{
0.5, 0.5027149505564014, 0.5054446430258502, 0.5081891574554764,
0.5109485743270583, 0.5137229745593818, 0.5165124395106142, 0.5193170509806894,
0.5221368912137069, 0.5249720429003435, 0.5278225891802786, 0.5306886136446309,
0.5335702003384117, 0.5364674337629877, 0.5393803988785598, 0.5423091811066545,
0.5452538663326288, 0.5482145409081883, 0.5511912916539204, 0.5541842058618393,
0.5571933712979462, 0.5602188762048033, 0.5632608093041209, 0.5663192597993595,
0.5693943173783458, 0.572486072215902, 0.5755946149764913, 0.5787200368168754,
0.5818624293887887, 0.585021884841625, 0.5881984958251406, 0.5913923554921704,
0.5946035575013605, 0.5978321960199137, 0.6010783657263515, 0.6043421618132907,
0.6076236799902344, 0.6109230164863786, 0.6142402680534349, 0.6175755319684665,
0.620928906036742, 0.6243004885946023, 0.6276903785123455, 0.6310986751971253,
0.6345254785958666, 0.637970889198196, 0.6414350080393891, 0.6449179367033329,
0.6484197773255048, 0.6519406325959679, 0.6554806057623822, 0.659039800633032,
0.6626183215798706, 0.6662162735415805, 0.6698337620266515, 0.6734708931164728,
0.6771277734684463, 0.6808045103191123, 0.6845012114872953, 0.688217985377265,
0.6919549409819159, 0.6957121878859629, 0.6994898362691555, 0.7032879969095076,
0.7071067811865475, 0.7109463010845827, 0.7148066691959849, 0.718687998724491,
0.7225904034885232, 0.7265139979245261, 0.7304588970903234, 0.7344252166684908,
0.7384130729697496, 0.7424225829363761, 0.7464538641456323, 0.7505070348132126,
0.7545822137967112, 0.7586795205991071, 0.762799075372269, 0.7669409989204777,
0.7711054127039704, 0.7752924388424999, 0.7795022001189185, 0.7837348199827764,
0.7879904225539431, 0.7922691326262467, 0.7965710756711334, 0.8008963778413465,
0.805245165974627, 0.8096175675974316, 0.8140137109286738, 0.8184337248834821,
0.8228777390769823, 0.8273458838280969, 0.8318382901633681, 0.8363550898207981,
0.8408964152537144, 0.8454623996346523, 0.8500531768592616, 0.8546688815502312,
0.8593096490612387, 0.8639756154809185, 0.8686669176368529, 0.8733836930995842,
0.8781260801866495, 0.8828942179666361, 0.8876882462632604, 0.8925083056594671,
0.8973545375015533, 0.9022270839033115, 0.9071260877501991, 0.9120516927035263,
0.9170040432046711, 0.9219832844793128, 0.9269895625416926, 0.9320230241988943,
0.9370838170551498, 0.9421720895161669, 0.9472879907934827, 0.9524316709088368,
0.9576032806985735, 0.9628029718180622, 0.9680308967461471, 0.9732872087896164,
0.9785720620876999, 0.9838856116165875, 0.9892280131939752, 0.9945994234836328,
},
// Schema 8:
{
0.5, 0.5013556375251013, 0.5027149505564014, 0.5040779490592088,
0.5054446430258502, 0.5068150424757447, 0.5081891574554764, 0.509566998038869,
0.5109485743270583, 0.5123338964485679, 0.5137229745593818, 0.5151158188430205,
0.5165124395106142, 0.5179128468009786, 0.5193170509806894, 0.520725062344158,
0.5221368912137069, 0.5235525479396449, 0.5249720429003435, 0.526395386502313,
0.5278225891802786, 0.5292536613972564, 0.5306886136446309, 0.5321274564422321,
0.5335702003384117, 0.5350168559101208, 0.5364674337629877, 0.5379219445313954,
0.5393803988785598, 0.5408428074966075, 0.5423091811066545, 0.5437795304588847,
0.5452538663326288, 0.5467321995364429, 0.5482145409081883, 0.549700901315111,
0.5511912916539204, 0.5526857228508706, 0.5541842058618393, 0.5556867516724088,
0.5571933712979462, 0.5587040757836845, 0.5602188762048033, 0.5617377836665098,
0.5632608093041209, 0.564787964283144, 0.5663192597993595, 0.5678547070789026,
0.5693943173783458, 0.5709381019847808, 0.572486072215902, 0.5740382394200894,
0.5755946149764913, 0.5771552102951081, 0.5787200368168754, 0.5802891060137493,
0.5818624293887887, 0.5834400184762408, 0.585021884841625, 0.5866080400818185,
0.5881984958251406, 0.5897932637314379, 0.5913923554921704, 0.5929957828304968,
0.5946035575013605, 0.5962156912915756, 0.5978321960199137, 0.5994530835371903,
0.6010783657263515, 0.6027080545025619, 0.6043421618132907, 0.6059806996384005,
0.6076236799902344, 0.6092711149137041, 0.6109230164863786, 0.6125793968185725,
0.6142402680534349, 0.6159056423670379, 0.6175755319684665, 0.6192499490999082,
0.620928906036742, 0.622612415087629, 0.6243004885946023, 0.6259931389331581,
0.6276903785123455, 0.6293922197748583, 0.6310986751971253, 0.6328097572894031,
0.6345254785958666, 0.6362458516947014, 0.637970889198196, 0.6397006037528346,
0.6414350080393891, 0.6431741147730128, 0.6449179367033329, 0.6466664866145447,
0.6484197773255048, 0.6501778216898253, 0.6519406325959679, 0.6537082229673385,
0.6554806057623822, 0.6572577939746774, 0.659039800633032, 0.6608266388015788,
0.6626183215798706, 0.6644148621029772, 0.6662162735415805, 0.6680225691020727,
0.6698337620266515, 0.6716498655934177, 0.6734708931164728, 0.6752968579460171,
0.6771277734684463, 0.6789636531064505, 0.6808045103191123, 0.6826503586020058,
0.6845012114872953, 0.6863570825438342, 0.688217985377265, 0.690083933630119,
0.6919549409819159, 0.6938310211492645, 0.6957121878859629, 0.6975984549830999,
0.6994898362691555, 0.7013863456101023, 0.7032879969095076, 0.7051948041086352,
0.7071067811865475, 0.7090239421602076, 0.7109463010845827, 0.7128738720527471,
0.7148066691959849, 0.7167447066838943, 0.718687998724491, 0.7206365595643126,
0.7225904034885232, 0.7245495448210174, 0.7265139979245261, 0.7284837772007218,
0.7304588970903234, 0.7324393720732029, 0.7344252166684908, 0.7364164454346837,
0.7384130729697496, 0.7404151139112358, 0.7424225829363761, 0.7444354947621984,
0.7464538641456323, 0.7484777058836176, 0.7505070348132126, 0.7525418658117031,
0.7545822137967112, 0.7566280937263048, 0.7586795205991071, 0.7607365094544071,
0.762799075372269, 0.7648672334736434, 0.7669409989204777, 0.7690203869158282,
0.7711054127039704, 0.7731960915705107, 0.7752924388424999, 0.7773944698885442,
0.7795022001189185, 0.7816156449856788, 0.7837348199827764, 0.7858597406461707,
0.7879904225539431, 0.7901268813264122, 0.7922691326262467, 0.7944171921585818,
0.7965710756711334, 0.7987307989543135, 0.8008963778413465, 0.8030678282083853,
0.805245165974627, 0.8074284071024302, 0.8096175675974316, 0.8118126635086642,
0.8140137109286738, 0.8162207259936375, 0.8184337248834821, 0.820652723822003,
0.8228777390769823, 0.8251087869603088, 0.8273458838280969, 0.8295890460808079,
0.8318382901633681, 0.8340936325652911, 0.8363550898207981, 0.8386226785089391,
0.8408964152537144, 0.8431763167241966, 0.8454623996346523, 0.8477546807446661,
0.8500531768592616, 0.8523579048290255, 0.8546688815502312, 0.8569861239649629,
0.8593096490612387, 0.8616394738731368, 0.8639756154809185, 0.8663180910111553,
0.8686669176368529, 0.871022112577578, 0.8733836930995842, 0.8757516765159389,
0.8781260801866495, 0.8805069215187917, 0.8828942179666361, 0.8852879870317771,
0.8876882462632604, 0.890095013257712, 0.8925083056594671, 0.8949281411607002,
0.8973545375015533, 0.8997875124702672, 0.9022270839033115, 0.9046732696855155,
0.9071260877501991, 0.909585556079304, 0.9120516927035263, 0.9145245157024483,
0.9170040432046711, 0.9194902933879467, 0.9219832844793128, 0.9244830347552253,
0.9269895625416926, 0.92950288621441, 0.9320230241988943, 0.9345499949706191,
0.9370838170551498, 0.93962450902828, 0.9421720895161669, 0.9447265771954693,
0.9472879907934827, 0.9498563490882775, 0.9524316709088368, 0.9550139751351947,
0.9576032806985735, 0.9601996065815236, 0.9628029718180622, 0.9654133954938133,
0.9680308967461471, 0.9706554947643201, 0.9732872087896164, 0.9759260581154889,
0.9785720620876999, 0.9812252401044634, 0.9838856116165875, 0.9865531961276168,
0.9892280131939752, 0.9919100824251095, 0.9945994234836328, 0.9972960560854698,
},
}

View File

@@ -0,0 +1,450 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package histogram
import (
"fmt"
"math"
"strings"
)
// CounterResetHint contains the known information about a counter reset,
// or alternatively that we are dealing with a gauge histogram, where counter resets do not apply.
type CounterResetHint byte
const (
UnknownCounterReset CounterResetHint = iota // UnknownCounterReset means we cannot say if this histogram signals a counter reset or not.
CounterReset // CounterReset means there was definitely a counter reset starting from this histogram.
NotCounterReset // NotCounterReset means there was definitely no counter reset with this histogram.
GaugeType // GaugeType means this is a gauge histogram, where counter resets do not happen.
)
// Histogram encodes a sparse, high-resolution histogram. See the design
// document for full details:
// https://docs.google.com/document/d/1cLNv3aufPZb3fNfaJgdaRBZsInZKKIHo9E6HinJVbpM/edit#
//
// The most tricky bit is how bucket indices represent real bucket boundaries.
// An example for schema 0 (by which each bucket is twice as wide as the
// previous bucket):
//
// Bucket boundaries → [-2,-1) [-1,-0.5) [-0.5,-0.25) ... [-0.001,0.001] ... (0.25,0.5] (0.5,1] (1,2] ....
// ↑ ↑ ↑ ↑ ↑ ↑ ↑
// Zero bucket (width e.g. 0.001) → | | | ZB | | |
// Positive bucket indices → | | | ... -1 0 1 2 3
// Negative bucket indices → 3 2 1 0 -1 ...
//
// Which bucket indices are actually used is determined by the spans.
type Histogram struct {
// Counter reset information.
CounterResetHint CounterResetHint
// Currently valid schema numbers are -4 <= n <= 8. They are all for
// base-2 bucket schemas, where 1 is a bucket boundary in each case, and
// then each power of two is divided into 2^n logarithmic buckets. Or
// in other words, each bucket boundary is the previous boundary times
// 2^(2^-n).
Schema int32
// Width of the zero bucket.
ZeroThreshold float64
// Observations falling into the zero bucket.
ZeroCount uint64
// Total number of observations.
Count uint64
// Sum of observations. This is also used as the stale marker.
Sum float64
// Spans for positive and negative buckets (see Span below).
PositiveSpans, NegativeSpans []Span
// Observation counts in buckets. The first element is an absolute
// count. All following ones are deltas relative to the previous
// element.
PositiveBuckets, NegativeBuckets []int64
}
// A Span defines a continuous sequence of buckets.
type Span struct {
// Gap to previous span (always positive), or starting index for the 1st
// span (which can be negative).
Offset int32
// Length of the span.
Length uint32
}
// Copy returns a deep copy of the Histogram.
func (h *Histogram) Copy() *Histogram {
c := *h
if len(h.PositiveSpans) != 0 {
c.PositiveSpans = make([]Span, len(h.PositiveSpans))
copy(c.PositiveSpans, h.PositiveSpans)
}
if len(h.NegativeSpans) != 0 {
c.NegativeSpans = make([]Span, len(h.NegativeSpans))
copy(c.NegativeSpans, h.NegativeSpans)
}
if len(h.PositiveBuckets) != 0 {
c.PositiveBuckets = make([]int64, len(h.PositiveBuckets))
copy(c.PositiveBuckets, h.PositiveBuckets)
}
if len(h.NegativeBuckets) != 0 {
c.NegativeBuckets = make([]int64, len(h.NegativeBuckets))
copy(c.NegativeBuckets, h.NegativeBuckets)
}
return &c
}
// String returns a string representation of the Histogram.
func (h *Histogram) String() string {
var sb strings.Builder
fmt.Fprintf(&sb, "{count:%d, sum:%g", h.Count, h.Sum)
var nBuckets []Bucket[uint64]
for it := h.NegativeBucketIterator(); it.Next(); {
bucket := it.At()
if bucket.Count != 0 {
nBuckets = append(nBuckets, it.At())
}
}
for i := len(nBuckets) - 1; i >= 0; i-- {
fmt.Fprintf(&sb, ", %s", nBuckets[i].String())
}
if h.ZeroCount != 0 {
fmt.Fprintf(&sb, ", %s", h.ZeroBucket().String())
}
for it := h.PositiveBucketIterator(); it.Next(); {
bucket := it.At()
if bucket.Count != 0 {
fmt.Fprintf(&sb, ", %s", bucket.String())
}
}
sb.WriteRune('}')
return sb.String()
}
// ZeroBucket returns the zero bucket.
func (h *Histogram) ZeroBucket() Bucket[uint64] {
return Bucket[uint64]{
Lower: -h.ZeroThreshold,
Upper: h.ZeroThreshold,
LowerInclusive: true,
UpperInclusive: true,
Count: h.ZeroCount,
}
}
// PositiveBucketIterator returns a BucketIterator to iterate over all positive
// buckets in ascending order (starting next to the zero bucket and going up).
func (h *Histogram) PositiveBucketIterator() BucketIterator[uint64] {
return newRegularBucketIterator(h.PositiveSpans, h.PositiveBuckets, h.Schema, true)
}
// NegativeBucketIterator returns a BucketIterator to iterate over all negative
// buckets in descending order (starting next to the zero bucket and going down).
func (h *Histogram) NegativeBucketIterator() BucketIterator[uint64] {
return newRegularBucketIterator(h.NegativeSpans, h.NegativeBuckets, h.Schema, false)
}
// CumulativeBucketIterator returns a BucketIterator to iterate over a
// cumulative view of the buckets. This method currently only supports
// Histograms without negative buckets and panics if the Histogram has negative
// buckets. It is currently only used for testing.
func (h *Histogram) CumulativeBucketIterator() BucketIterator[uint64] {
if len(h.NegativeBuckets) > 0 {
panic("CumulativeBucketIterator called on Histogram with negative buckets")
}
return &cumulativeBucketIterator{h: h, posSpansIdx: -1}
}
// Equals returns true if the given histogram matches exactly.
// Exact match is when there are no new buckets (even empty) and no missing buckets,
// and all the bucket values match. Spans can have different empty length spans in between,
// but they must represent the same bucket layout to match.
func (h *Histogram) Equals(h2 *Histogram) bool {
if h2 == nil {
return false
}
if h.Schema != h2.Schema || h.ZeroThreshold != h2.ZeroThreshold ||
h.ZeroCount != h2.ZeroCount || h.Count != h2.Count || h.Sum != h2.Sum {
return false
}
if !spansMatch(h.PositiveSpans, h2.PositiveSpans) {
return false
}
if !spansMatch(h.NegativeSpans, h2.NegativeSpans) {
return false
}
if !bucketsMatch(h.PositiveBuckets, h2.PositiveBuckets) {
return false
}
if !bucketsMatch(h.NegativeBuckets, h2.NegativeBuckets) {
return false
}
return true
}
// spansMatch returns true if both spans represent the same bucket layout
// after combining zero length spans with the next non-zero length span.
func spansMatch(s1, s2 []Span) bool {
if len(s1) == 0 && len(s2) == 0 {
return true
}
s1idx, s2idx := 0, 0
for {
if s1idx >= len(s1) {
return allEmptySpans(s2[s2idx:])
}
if s2idx >= len(s2) {
return allEmptySpans(s1[s1idx:])
}
currS1, currS2 := s1[s1idx], s2[s2idx]
s1idx++
s2idx++
if currS1.Length == 0 {
// This span is zero length, so we add consecutive such spans
// until we find a non-zero span.
for ; s1idx < len(s1) && s1[s1idx].Length == 0; s1idx++ {
currS1.Offset += s1[s1idx].Offset
}
if s1idx < len(s1) {
currS1.Offset += s1[s1idx].Offset
currS1.Length = s1[s1idx].Length
s1idx++
}
}
if currS2.Length == 0 {
// This span is zero length, so we add consecutive such spans
// until we find a non-zero span.
for ; s2idx < len(s2) && s2[s2idx].Length == 0; s2idx++ {
currS2.Offset += s2[s2idx].Offset
}
if s2idx < len(s2) {
currS2.Offset += s2[s2idx].Offset
currS2.Length = s2[s2idx].Length
s2idx++
}
}
if currS1.Length == 0 && currS2.Length == 0 {
// The last spans of both set are zero length. Previous spans match.
return true
}
if currS1.Offset != currS2.Offset || currS1.Length != currS2.Length {
return false
}
}
}
func allEmptySpans(s []Span) bool {
for _, ss := range s {
if ss.Length > 0 {
return false
}
}
return true
}
// Compact works like FloatHistogram.Compact. See there for detailed
// explanations.
func (h *Histogram) Compact(maxEmptyBuckets int) *Histogram {
h.PositiveBuckets, h.PositiveSpans = compactBuckets(
h.PositiveBuckets, h.PositiveSpans, maxEmptyBuckets, true,
)
h.NegativeBuckets, h.NegativeSpans = compactBuckets(
h.NegativeBuckets, h.NegativeSpans, maxEmptyBuckets, true,
)
return h
}
// ToFloat returns a FloatHistogram representation of the Histogram. It is a
// deep copy (e.g. spans are not shared).
func (h *Histogram) ToFloat() *FloatHistogram {
var (
positiveSpans, negativeSpans []Span
positiveBuckets, negativeBuckets []float64
)
if len(h.PositiveSpans) != 0 {
positiveSpans = make([]Span, len(h.PositiveSpans))
copy(positiveSpans, h.PositiveSpans)
}
if len(h.NegativeSpans) != 0 {
negativeSpans = make([]Span, len(h.NegativeSpans))
copy(negativeSpans, h.NegativeSpans)
}
if len(h.PositiveBuckets) != 0 {
positiveBuckets = make([]float64, len(h.PositiveBuckets))
var current float64
for i, b := range h.PositiveBuckets {
current += float64(b)
positiveBuckets[i] = current
}
}
if len(h.NegativeBuckets) != 0 {
negativeBuckets = make([]float64, len(h.NegativeBuckets))
var current float64
for i, b := range h.NegativeBuckets {
current += float64(b)
negativeBuckets[i] = current
}
}
return &FloatHistogram{
CounterResetHint: h.CounterResetHint,
Schema: h.Schema,
ZeroThreshold: h.ZeroThreshold,
ZeroCount: float64(h.ZeroCount),
Count: float64(h.Count),
Sum: h.Sum,
PositiveSpans: positiveSpans,
NegativeSpans: negativeSpans,
PositiveBuckets: positiveBuckets,
NegativeBuckets: negativeBuckets,
}
}
type regularBucketIterator struct {
baseBucketIterator[uint64, int64]
}
func newRegularBucketIterator(spans []Span, buckets []int64, schema int32, positive bool) *regularBucketIterator {
i := baseBucketIterator[uint64, int64]{
schema: schema,
spans: spans,
buckets: buckets,
positive: positive,
}
return &regularBucketIterator{i}
}
func (r *regularBucketIterator) Next() bool {
if r.spansIdx >= len(r.spans) {
return false
}
span := r.spans[r.spansIdx]
// Seed currIdx for the first bucket.
if r.bucketsIdx == 0 {
r.currIdx = span.Offset
} else {
r.currIdx++
}
for r.idxInSpan >= span.Length {
// We have exhausted the current span and have to find a new
// one. We'll even handle pathologic spans of length 0.
r.idxInSpan = 0
r.spansIdx++
if r.spansIdx >= len(r.spans) {
return false
}
span = r.spans[r.spansIdx]
r.currIdx += span.Offset
}
r.currCount += r.buckets[r.bucketsIdx]
r.idxInSpan++
r.bucketsIdx++
return true
}
type cumulativeBucketIterator struct {
h *Histogram
posSpansIdx int // Index in h.PositiveSpans we are in. -1 means 0 bucket.
posBucketsIdx int // Index in h.PositiveBuckets.
idxInSpan uint32 // Index in the current span. 0 <= idxInSpan < span.Length.
initialized bool
currIdx int32 // The actual bucket index after decoding from spans.
currUpper float64 // The upper boundary of the current bucket.
currCount int64 // Current non-cumulative count for the current bucket. Does not apply for empty bucket.
currCumulativeCount uint64 // Current "cumulative" count for the current bucket.
// Between 2 spans there could be some empty buckets which
// still needs to be counted for cumulative buckets.
// When we hit the end of a span, we use this to iterate
// through the empty buckets.
emptyBucketCount int32
}
func (c *cumulativeBucketIterator) Next() bool {
if c.posSpansIdx == -1 {
// Zero bucket.
c.posSpansIdx++
if c.h.ZeroCount == 0 {
return c.Next()
}
c.currUpper = c.h.ZeroThreshold
c.currCount = int64(c.h.ZeroCount)
c.currCumulativeCount = uint64(c.currCount)
return true
}
if c.posSpansIdx >= len(c.h.PositiveSpans) {
return false
}
if c.emptyBucketCount > 0 {
// We are traversing through empty buckets at the moment.
c.currUpper = getBound(c.currIdx, c.h.Schema)
c.currIdx++
c.emptyBucketCount--
return true
}
span := c.h.PositiveSpans[c.posSpansIdx]
if c.posSpansIdx == 0 && !c.initialized {
// Initializing.
c.currIdx = span.Offset
// The first bucket is an absolute value and not a delta with Zero bucket.
c.currCount = 0
c.initialized = true
}
c.currCount += c.h.PositiveBuckets[c.posBucketsIdx]
c.currCumulativeCount += uint64(c.currCount)
c.currUpper = getBound(c.currIdx, c.h.Schema)
c.posBucketsIdx++
c.idxInSpan++
c.currIdx++
if c.idxInSpan >= span.Length {
// Move to the next span. This one is done.
c.posSpansIdx++
c.idxInSpan = 0
if c.posSpansIdx < len(c.h.PositiveSpans) {
c.emptyBucketCount = c.h.PositiveSpans[c.posSpansIdx].Offset
}
}
return true
}
func (c *cumulativeBucketIterator) At() Bucket[uint64] {
return Bucket[uint64]{
Upper: c.currUpper,
Lower: math.Inf(-1),
UpperInclusive: true,
LowerInclusive: true,
Count: c.currCumulativeCount,
Index: c.currIdx - 1,
}
}

View File

@@ -19,7 +19,8 @@ import (
"sort"
"strconv"
"github.com/cespare/xxhash"
"github.com/cespare/xxhash/v2"
"github.com/prometheus/common/model"
)
// Well-known label names used by Prometheus components.
@@ -29,10 +30,11 @@ const (
BucketLabel = "le"
InstanceName = "instance"
sep = '\xff'
labelSep = '\xfe'
)
var seps = []byte{'\xff'}
// Label is a key/value pair of strings.
type Label struct {
Name, Value string
@@ -70,10 +72,10 @@ func (ls Labels) Bytes(buf []byte) []byte {
b.WriteByte(labelSep)
for i, l := range ls {
if i > 0 {
b.WriteByte(sep)
b.WriteByte(seps[0])
}
b.WriteString(l.Name)
b.WriteByte(sep)
b.WriteByte(seps[0])
b.WriteString(l.Value)
}
return b.Bytes()
@@ -118,7 +120,7 @@ func (ls *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (ls Labels) MatchLabels(on bool, names ...string) Labels {
matchedLabels := Labels{}
nameSet := map[string]struct{}{}
nameSet := make(map[string]struct{}, len(names))
for _, n := range names {
nameSet[n] = struct{}{}
}
@@ -133,14 +135,28 @@ func (ls Labels) MatchLabels(on bool, names ...string) Labels {
}
// Hash returns a hash value for the label set.
// Note: the result is not guaranteed to be consistent across different runs of Prometheus.
func (ls Labels) Hash() uint64 {
// Use xxhash.Sum64(b) for fast path as it's faster.
b := make([]byte, 0, 1024)
for i, v := range ls {
if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
// If labels entry is 1KB+ do not allocate whole entry.
h := xxhash.New()
_, _ = h.Write(b)
for _, v := range ls[i:] {
_, _ = h.WriteString(v.Name)
_, _ = h.Write(seps)
_, _ = h.WriteString(v.Value)
_, _ = h.Write(seps)
}
return h.Sum64()
}
for _, v := range ls {
b = append(b, v.Name...)
b = append(b, sep)
b = append(b, seps[0])
b = append(b, v.Value...)
b = append(b, sep)
b = append(b, seps[0])
}
return xxhash.Sum64(b)
}
@@ -157,9 +173,9 @@ func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
i++
} else {
b = append(b, ls[i].Name...)
b = append(b, sep)
b = append(b, seps[0])
b = append(b, ls[i].Value...)
b = append(b, sep)
b = append(b, seps[0])
i++
j++
}
@@ -181,18 +197,18 @@ func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
continue
}
b = append(b, ls[i].Name...)
b = append(b, sep)
b = append(b, seps[0])
b = append(b, ls[i].Value...)
b = append(b, sep)
b = append(b, seps[0])
}
return xxhash.Sum64(b), b
}
// WithLabels returns a new labels.Labels from ls that only contains labels matching names.
// BytesWithLabels is just as Bytes(), but only for labels matching names.
// 'names' have to be sorted in ascending order.
func (ls Labels) WithLabels(names ...string) Labels {
ret := make([]Label, 0, len(ls))
func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte {
b := bytes.NewBuffer(buf[:0])
b.WriteByte(labelSep)
i, j := 0, 0
for i < len(ls) && j < len(names) {
if names[j] < ls[i].Name {
@@ -200,30 +216,40 @@ func (ls Labels) WithLabels(names ...string) Labels {
} else if ls[i].Name < names[j] {
i++
} else {
ret = append(ret, ls[i])
if b.Len() > 1 {
b.WriteByte(seps[0])
}
b.WriteString(ls[i].Name)
b.WriteByte(seps[0])
b.WriteString(ls[i].Value)
i++
j++
}
}
return ret
return b.Bytes()
}
// WithoutLabels returns a new labels.Labels from ls that contains labels not matching names.
// BytesWithoutLabels is just as Bytes(), but only for labels not matching names.
// 'names' have to be sorted in ascending order.
func (ls Labels) WithoutLabels(names ...string) Labels {
ret := make([]Label, 0, len(ls))
func (ls Labels) BytesWithoutLabels(buf []byte, names ...string) []byte {
b := bytes.NewBuffer(buf[:0])
b.WriteByte(labelSep)
j := 0
for i := range ls {
for j < len(names) && names[j] < ls[i].Name {
j++
}
if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) {
if j < len(names) && ls[i].Name == names[j] {
continue
}
ret = append(ret, ls[i])
if b.Len() > 1 {
b.WriteByte(seps[0])
}
b.WriteString(ls[i].Name)
b.WriteByte(seps[0])
b.WriteString(ls[i].Value)
}
return ret
return b.Bytes()
}
// Copy returns a copy of the labels.
@@ -275,6 +301,7 @@ func (ls Labels) WithoutEmpty() Labels {
if v.Value != "" {
continue
}
// Do not copy the slice until it's necessary.
els := make(Labels, 0, len(ls)-1)
for _, v := range ls {
if v.Value != "" {
@@ -286,13 +313,26 @@ func (ls Labels) WithoutEmpty() Labels {
return ls
}
// IsValid checks if the metric name or label names are valid.
func (ls Labels) IsValid() bool {
for _, l := range ls {
if l.Name == model.MetricNameLabel && !model.IsValidMetricName(model.LabelValue(l.Value)) {
return false
}
if !model.LabelName(l.Name).IsValid() || !model.LabelValue(l.Value).IsValid() {
return false
}
}
return true
}
// Equal returns whether the two label sets are equal.
func Equal(ls, o Labels) bool {
if len(ls) != len(o) {
return false
}
for i, l := range ls {
if l.Name != o[i].Name || l.Value != o[i].Value {
if l != o[i] {
return false
}
}
@@ -308,13 +348,16 @@ func (ls Labels) Map() map[string]string {
return m
}
// EmptyLabels returns n empty Labels value, for convenience.
func EmptyLabels() Labels {
return Labels{}
}
// New returns a sorted Labels from the given labels.
// The caller has to guarantee that all label names are unique.
func New(ls ...Label) Labels {
set := make(Labels, 0, len(ls))
for _, l := range ls {
set = append(set, l)
}
set = append(set, ls...)
sort.Sort(set)
return set
@@ -334,7 +377,7 @@ func FromStrings(ss ...string) Labels {
if len(ss)%2 != 0 {
panic("invalid number of strings")
}
var res Labels
res := make(Labels, 0, len(ss)/2)
for i := 0; i < len(ss); i += 2 {
res = append(res, Label{Name: ss[i], Value: ss[i+1]})
}
@@ -369,6 +412,49 @@ func Compare(a, b Labels) int {
return len(a) - len(b)
}
// Copy labels from b on top of whatever was in ls previously, reusing memory or expanding if needed.
func (ls *Labels) CopyFrom(b Labels) {
(*ls) = append((*ls)[:0], b...)
}
// IsEmpty returns true if ls represents an empty set of labels.
func (ls Labels) IsEmpty() bool {
return len(ls) == 0
}
// Range calls f on each label.
func (ls Labels) Range(f func(l Label)) {
for _, l := range ls {
f(l)
}
}
// Validate calls f on each label. If f returns a non-nil error, then it returns that error cancelling the iteration.
func (ls Labels) Validate(f func(l Label) error) error {
for _, l := range ls {
if err := f(l); err != nil {
return err
}
}
return nil
}
// InternStrings calls intern on every string value inside ls, replacing them with what it returns.
func (ls *Labels) InternStrings(intern func(string) string) {
for i, l := range *ls {
(*ls)[i].Name = intern(l.Name)
(*ls)[i].Value = intern(l.Value)
}
}
// ReleaseStrings calls release on every string value inside ls.
func (ls Labels) ReleaseStrings(release func(string)) {
for _, l := range ls {
release(l.Name)
release(l.Value)
}
}
// Builder allows modifying Labels.
type Builder struct {
base Labels
@@ -411,7 +497,21 @@ func (b *Builder) Del(ns ...string) *Builder {
return b
}
// Set the name/value pair as a label.
// Keep removes all labels from the base except those with the given names.
func (b *Builder) Keep(ns ...string) *Builder {
Outer:
for _, l := range b.base {
for _, n := range ns {
if l.Name == n {
continue Outer
}
}
b.del = append(b.del, l.Name)
}
return b
}
// Set the name/value pair as a label. A value of "" means delete that label.
func (b *Builder) Set(n, v string) *Builder {
if v == "" {
// Empty labels are the same as missing labels.
@@ -428,17 +528,25 @@ func (b *Builder) Set(n, v string) *Builder {
return b
}
// Labels returns the labels from the builder. If no modifications
// were made, the original labels are returned.
func (b *Builder) Labels() Labels {
// Labels returns the labels from the builder, adding them to res if non-nil.
// Argument res can be the same as b.base, if caller wants to overwrite that slice.
// If no modifications were made, the original labels are returned.
func (b *Builder) Labels(res Labels) Labels {
if len(b.del) == 0 && len(b.add) == 0 {
return b.base
}
// In the general case, labels are removed, modified or moved
// rather than added.
res := make(Labels, 0, len(b.base))
if res == nil {
// In the general case, labels are removed, modified or moved
// rather than added.
res = make(Labels, 0, len(b.base))
} else {
res = res[:0]
}
Outer:
// Justification that res can be the same slice as base: in this loop
// we move forward through base, and either skip an element or assign
// it to res at its current position or an earlier position.
for _, l := range b.base {
for _, n := range b.del {
if l.Name == n {
@@ -452,8 +560,46 @@ Outer:
}
res = append(res, l)
}
res = append(res, b.add...)
sort.Sort(res)
if len(b.add) > 0 { // Base is already in order, so we only need to sort if we add to it.
res = append(res, b.add...)
sort.Sort(res)
}
return res
}
// ScratchBuilder allows efficient construction of a Labels from scratch.
type ScratchBuilder struct {
add Labels
}
// NewScratchBuilder creates a ScratchBuilder initialized for Labels with n entries.
func NewScratchBuilder(n int) ScratchBuilder {
return ScratchBuilder{add: make([]Label, 0, n)}
}
func (b *ScratchBuilder) Reset() {
b.add = b.add[:0]
}
// Add a name/value pair.
// Note if you Add the same name twice you will get a duplicate label, which is invalid.
func (b *ScratchBuilder) Add(name, value string) {
b.add = append(b.add, Label{Name: name, Value: value})
}
// Sort the labels added so far by name.
func (b *ScratchBuilder) Sort() {
sort.Sort(b.add)
}
// Asssign is for when you already have a Labels which you want this ScratchBuilder to return.
func (b *ScratchBuilder) Assign(ls Labels) {
b.add = append(b.add[:0], ls...) // Copy on top of our slice, so we don't retain the input slice.
}
// Return the name/value pairs added so far as a Labels object.
// Note: if you want them sorted, call Sort() first.
func (b *ScratchBuilder) Labels() Labels {
// Copy the slice, so the next use of ScratchBuilder doesn't overwrite.
return append([]Label{}, b.add...)
}

View File

@@ -28,17 +28,18 @@ const (
MatchNotRegexp
)
var matchTypeToStr = [...]string{
MatchEqual: "=",
MatchNotEqual: "!=",
MatchRegexp: "=~",
MatchNotRegexp: "!~",
}
func (m MatchType) String() string {
typeToStr := map[MatchType]string{
MatchEqual: "=",
MatchNotEqual: "!=",
MatchRegexp: "=~",
MatchNotRegexp: "!~",
if m < MatchEqual || m > MatchNotRegexp {
panic("unknown match type")
}
if str, ok := typeToStr[m]; ok {
return str
}
panic("unknown match type")
return matchTypeToStr[m]
}
// Matcher models the matching of a label.

View File

@@ -14,9 +14,10 @@
package labels
import (
"regexp"
"regexp/syntax"
"strings"
"github.com/grafana/regexp"
"github.com/grafana/regexp/syntax"
)
type FastRegexMatcher struct {
@@ -86,10 +87,10 @@ func optimizeConcatRegex(r *syntax.Regexp) (prefix, suffix, contains string) {
// Given Prometheus regex matchers are always anchored to the begin/end
// of the text, if the first/last operations are literals, we can safely
// treat them as prefix/suffix.
if sub[0].Op == syntax.OpLiteral {
if sub[0].Op == syntax.OpLiteral && (sub[0].Flags&syntax.FoldCase) == 0 {
prefix = string(sub[0].Rune)
}
if last := len(sub) - 1; sub[last].Op == syntax.OpLiteral {
if last := len(sub) - 1; sub[last].Op == syntax.OpLiteral && (sub[last].Flags&syntax.FoldCase) == 0 {
suffix = string(sub[last].Rune)
}
@@ -97,7 +98,7 @@ func optimizeConcatRegex(r *syntax.Regexp) (prefix, suffix, contains string) {
// 1st one. We do not keep the whole list of literals to simplify the
// fast path.
for i := 1; i < len(sub)-1; i++ {
if sub[i].Op == syntax.OpLiteral {
if sub[i].Op == syntax.OpLiteral && (sub[i].Flags&syntax.FoldCase) == 0 {
contains = string(sub[i].Rune)
break
}

View File

@@ -15,11 +15,9 @@ package labels
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
"github.com/pkg/errors"
)
// Slice is a sortable slice of label sets.
@@ -52,13 +50,14 @@ func ReadLabels(fn string, n int) ([]Labels, error) {
defer f.Close()
scanner := bufio.NewScanner(f)
b := ScratchBuilder{}
var mets []Labels
hashes := map[uint64]struct{}{}
i := 0
for scanner.Scan() && i < n {
m := make(Labels, 0, 10)
b.Reset()
r := strings.NewReplacer("\"", "", "{", "", "}", "")
s := r.Replace(scanner.Text())
@@ -66,10 +65,11 @@ func ReadLabels(fn string, n int) ([]Labels, error) {
labelChunks := strings.Split(s, ",")
for _, labelChunk := range labelChunks {
split := strings.Split(labelChunk, ":")
m = append(m, Label{Name: split[0], Value: split[1]})
b.Add(split[0], split[1])
}
// Order of the k/v labels matters, don't assume we'll always receive them already sorted.
sort.Sort(m)
b.Sort()
m := b.Labels()
h := m.Hash()
if _, ok := hashes[h]; ok {
@@ -81,7 +81,7 @@ func ReadLabels(fn string, n int) ([]Labels, error) {
}
if i != n {
return mets, errors.Errorf("requested %d metrics but found %d", n, i)
return mets, fmt.Errorf("requested %d metrics but found %d", n, i)
}
return mets, nil
}

View File

@@ -0,0 +1,23 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metadata
import "github.com/prometheus/prometheus/model/textparse"
// Metadata stores a series' metadata information.
type Metadata struct {
Type textparse.MetricType
Unit string
Help string
}

View File

@@ -0,0 +1,309 @@
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package relabel
import (
"crypto/md5"
"fmt"
"strings"
"github.com/grafana/regexp"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
)
var (
relabelTarget = regexp.MustCompile(`^(?:(?:[a-zA-Z_]|\$(?:\{\w+\}|\w+))+\w*)+$`)
DefaultRelabelConfig = Config{
Action: Replace,
Separator: ";",
Regex: MustNewRegexp("(.*)"),
Replacement: "$1",
}
)
// Action is the action to be performed on relabeling.
type Action string
const (
// Replace performs a regex replacement.
Replace Action = "replace"
// Keep drops targets for which the input does not match the regex.
Keep Action = "keep"
// Drop drops targets for which the input does match the regex.
Drop Action = "drop"
// KeepEqual drops targets for which the input does not match the target.
KeepEqual Action = "keepequal"
// Drop drops targets for which the input does match the target.
DropEqual Action = "dropequal"
// HashMod sets a label to the modulus of a hash of labels.
HashMod Action = "hashmod"
// LabelMap copies labels to other labelnames based on a regex.
LabelMap Action = "labelmap"
// LabelDrop drops any label matching the regex.
LabelDrop Action = "labeldrop"
// LabelKeep drops any label not matching the regex.
LabelKeep Action = "labelkeep"
// Lowercase maps input letters to their lower case.
Lowercase Action = "lowercase"
// Uppercase maps input letters to their upper case.
Uppercase Action = "uppercase"
)
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (a *Action) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
switch act := Action(strings.ToLower(s)); act {
case Replace, Keep, Drop, HashMod, LabelMap, LabelDrop, LabelKeep, Lowercase, Uppercase, KeepEqual, DropEqual:
*a = act
return nil
}
return fmt.Errorf("unknown relabel action %q", s)
}
// Config is the configuration for relabeling of target label sets.
type Config struct {
// A list of labels from which values are taken and concatenated
// with the configured separator in order.
SourceLabels model.LabelNames `yaml:"source_labels,flow,omitempty"`
// Separator is the string between concatenated values from the source labels.
Separator string `yaml:"separator,omitempty"`
// Regex against which the concatenation is matched.
Regex Regexp `yaml:"regex,omitempty"`
// Modulus to take of the hash of concatenated values from the source labels.
Modulus uint64 `yaml:"modulus,omitempty"`
// TargetLabel is the label to which the resulting string is written in a replacement.
// Regexp interpolation is allowed for the replace action.
TargetLabel string `yaml:"target_label,omitempty"`
// Replacement is the regex replacement pattern to be used.
Replacement string `yaml:"replacement,omitempty"`
// Action is the action to be performed for the relabeling.
Action Action `yaml:"action,omitempty"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultRelabelConfig
type plain Config
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Regex.Regexp == nil {
c.Regex = MustNewRegexp("")
}
if c.Action == "" {
return fmt.Errorf("relabel action cannot be empty")
}
if c.Modulus == 0 && c.Action == HashMod {
return fmt.Errorf("relabel configuration for hashmod requires non-zero modulus")
}
if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.TargetLabel == "" {
return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action)
}
if (c.Action == Replace || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !relabelTarget.MatchString(c.TargetLabel) {
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
}
if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.Replacement != DefaultRelabelConfig.Replacement {
return fmt.Errorf("'replacement' can not be set for %s action", c.Action)
}
if c.Action == LabelMap && !relabelTarget.MatchString(c.Replacement) {
return fmt.Errorf("%q is invalid 'replacement' for %s action", c.Replacement, c.Action)
}
if c.Action == HashMod && !model.LabelName(c.TargetLabel).IsValid() {
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
}
if c.Action == DropEqual || c.Action == KeepEqual {
if c.Regex != DefaultRelabelConfig.Regex ||
c.Modulus != DefaultRelabelConfig.Modulus ||
c.Separator != DefaultRelabelConfig.Separator ||
c.Replacement != DefaultRelabelConfig.Replacement {
return fmt.Errorf("%s action requires only 'source_labels' and `target_label`, and no other fields", c.Action)
}
}
if c.Action == LabelDrop || c.Action == LabelKeep {
if c.SourceLabels != nil ||
c.TargetLabel != DefaultRelabelConfig.TargetLabel ||
c.Modulus != DefaultRelabelConfig.Modulus ||
c.Separator != DefaultRelabelConfig.Separator ||
c.Replacement != DefaultRelabelConfig.Replacement {
return fmt.Errorf("%s action requires only 'regex', and no other fields", c.Action)
}
}
return nil
}
// Regexp encapsulates a regexp.Regexp and makes it YAML marshalable.
type Regexp struct {
*regexp.Regexp
}
// NewRegexp creates a new anchored Regexp and returns an error if the
// passed-in regular expression does not compile.
func NewRegexp(s string) (Regexp, error) {
regex, err := regexp.Compile("^(?:" + s + ")$")
return Regexp{Regexp: regex}, err
}
// MustNewRegexp works like NewRegexp, but panics if the regular expression does not compile.
func MustNewRegexp(s string) Regexp {
re, err := NewRegexp(s)
if err != nil {
panic(err)
}
return re
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
r, err := NewRegexp(s)
if err != nil {
return err
}
*re = r
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
func (re Regexp) MarshalYAML() (interface{}, error) {
if re.String() != "" {
return re.String(), nil
}
return nil, nil
}
// String returns the original string used to compile the regular expression.
func (re Regexp) String() string {
str := re.Regexp.String()
// Trim the anchor `^(?:` prefix and `)$` suffix.
return str[4 : len(str)-2]
}
// Process returns a relabeled copy of the given label set. The relabel configurations
// are applied in order of input.
// If a label set is dropped, EmptyLabels and false is returned.
// May return the input labelSet modified.
func Process(lbls labels.Labels, cfgs ...*Config) (ret labels.Labels, keep bool) {
lb := labels.NewBuilder(labels.EmptyLabels())
for _, cfg := range cfgs {
lbls, keep = relabel(lbls, cfg, lb)
if !keep {
return labels.EmptyLabels(), false
}
}
return lbls, true
}
func relabel(lset labels.Labels, cfg *Config, lb *labels.Builder) (ret labels.Labels, keep bool) {
var va [16]string
values := va[:0]
if len(cfg.SourceLabels) > cap(values) {
values = make([]string, 0, len(cfg.SourceLabels))
}
for _, ln := range cfg.SourceLabels {
values = append(values, lset.Get(string(ln)))
}
val := strings.Join(values, cfg.Separator)
lb.Reset(lset)
switch cfg.Action {
case Drop:
if cfg.Regex.MatchString(val) {
return labels.EmptyLabels(), false
}
case Keep:
if !cfg.Regex.MatchString(val) {
return labels.EmptyLabels(), false
}
case DropEqual:
if lset.Get(cfg.TargetLabel) == val {
return labels.EmptyLabels(), false
}
case KeepEqual:
if lset.Get(cfg.TargetLabel) != val {
return labels.EmptyLabels(), false
}
case Replace:
indexes := cfg.Regex.FindStringSubmatchIndex(val)
// If there is no match no replacement must take place.
if indexes == nil {
break
}
target := model.LabelName(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes))
if !target.IsValid() {
lb.Del(cfg.TargetLabel)
break
}
res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes)
if len(res) == 0 {
lb.Del(cfg.TargetLabel)
break
}
lb.Set(string(target), string(res))
case Lowercase:
lb.Set(cfg.TargetLabel, strings.ToLower(val))
case Uppercase:
lb.Set(cfg.TargetLabel, strings.ToUpper(val))
case HashMod:
mod := sum64(md5.Sum([]byte(val))) % cfg.Modulus
lb.Set(cfg.TargetLabel, fmt.Sprintf("%d", mod))
case LabelMap:
lset.Range(func(l labels.Label) {
if cfg.Regex.MatchString(l.Name) {
res := cfg.Regex.ReplaceAllString(l.Name, cfg.Replacement)
lb.Set(res, l.Value)
}
})
case LabelDrop:
lset.Range(func(l labels.Label) {
if cfg.Regex.MatchString(l.Name) {
lb.Del(l.Name)
}
})
case LabelKeep:
lset.Range(func(l labels.Label) {
if !cfg.Regex.MatchString(l.Name) {
lb.Del(l.Name)
}
})
default:
panic(fmt.Errorf("relabel: unknown relabel action type %q", cfg.Action))
}
return lb.Labels(lset), true
}
// sum64 sums the md5 hash to an uint64.
func sum64(hash [md5.Size]byte) uint64 {
var s uint64
for i, b := range hash {
shift := uint64((md5.Size - i - 1) * 8)
s |= uint64(b) << shift
}
return s
}

View File

@@ -16,16 +16,17 @@ package rulefmt
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"time"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
"github.com/prometheus/prometheus/pkg/timestamp"
"github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/template"
)
@@ -38,6 +39,25 @@ type Error struct {
Err WrappedError
}
// Error prints the error message in a formatted string.
func (err *Error) Error() string {
if err.Err.err == nil {
return ""
}
if err.Err.nodeAlt != nil {
return fmt.Sprintf("%d:%d: %d:%d: group %q, rule %d, %q: %v", err.Err.node.Line, err.Err.node.Column, err.Err.nodeAlt.Line, err.Err.nodeAlt.Column, err.Group, err.Rule, err.RuleName, err.Err.err)
}
if err.Err.node != nil {
return fmt.Sprintf("%d:%d: group %q, rule %d, %q: %v", err.Err.node.Line, err.Err.node.Column, err.Group, err.Rule, err.RuleName, err.Err.err)
}
return fmt.Sprintf("group %q, rule %d, %q: %v", err.Group, err.Rule, err.RuleName, err.Err.err)
}
// Unwrap unpacks wrapped error for use in errors.Is & errors.As.
func (err *Error) Unwrap() error {
return &err.Err
}
// WrappedError wraps error with the yaml node which can be used to represent
// the line and column numbers of the error.
type WrappedError struct {
@@ -46,13 +66,23 @@ type WrappedError struct {
nodeAlt *yaml.Node
}
func (err *Error) Error() string {
if err.Err.nodeAlt != nil {
return errors.Wrapf(err.Err.err, "%d:%d: %d:%d: group %q, rule %d, %q", err.Err.node.Line, err.Err.node.Column, err.Err.nodeAlt.Line, err.Err.nodeAlt.Column, err.Group, err.Rule, err.RuleName).Error()
} else if err.Err.node != nil {
return errors.Wrapf(err.Err.err, "%d:%d: group %q, rule %d, %q", err.Err.node.Line, err.Err.node.Column, err.Group, err.Rule, err.RuleName).Error()
// Error prints the error message in a formatted string.
func (we *WrappedError) Error() string {
if we.err == nil {
return ""
}
return errors.Wrapf(err.Err.err, "group %q, rule %d, %q", err.Group, err.Rule, err.RuleName).Error()
if we.nodeAlt != nil {
return fmt.Sprintf("%d:%d: %d:%d: %v", we.node.Line, we.node.Column, we.nodeAlt.Line, we.nodeAlt.Column, we.err)
}
if we.node != nil {
return fmt.Sprintf("%d:%d: %v", we.node.Line, we.node.Column, we.err)
}
return we.err.Error()
}
// Unwrap unpacks wrapped error for use in errors.Is & errors.As.
func (we *WrappedError) Unwrap() error {
return we.err
}
// RuleGroups is a set of rule groups that are typically exposed in a file.
@@ -70,20 +100,20 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
for j, g := range g.Groups {
if g.Name == "" {
errs = append(errs, errors.Errorf("%d:%d: Groupname must not be empty", node.Groups[j].Line, node.Groups[j].Column))
errs = append(errs, fmt.Errorf("%d:%d: Groupname must not be empty", node.Groups[j].Line, node.Groups[j].Column))
}
if _, ok := set[g.Name]; ok {
errs = append(
errs,
errors.Errorf("%d:%d: groupname: \"%s\" is repeated in the same file", node.Groups[j].Line, node.Groups[j].Column, g.Name),
fmt.Errorf("%d:%d: groupname: \"%s\" is repeated in the same file", node.Groups[j].Line, node.Groups[j].Column, g.Name),
)
}
set[g.Name] = struct{}{}
for i, r := range g.Rules {
for _, node := range r.Validate() {
for _, node := range g.Rules[i].Validate() {
var ruleName yaml.Node
if r.Alert.Value != "" {
ruleName = r.Alert
@@ -107,34 +137,37 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
type RuleGroup struct {
Name string `yaml:"name"`
Interval model.Duration `yaml:"interval,omitempty"`
Limit int `yaml:"limit,omitempty"`
Rules []RuleNode `yaml:"rules"`
}
// Rule describes an alerting or recording rule.
type Rule struct {
Record string `yaml:"record,omitempty"`
Alert string `yaml:"alert,omitempty"`
Expr string `yaml:"expr"`
For model.Duration `yaml:"for,omitempty"`
Labels map[string]string `yaml:"labels,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty"`
Record string `yaml:"record,omitempty"`
Alert string `yaml:"alert,omitempty"`
Expr string `yaml:"expr"`
For model.Duration `yaml:"for,omitempty"`
KeepFiringFor model.Duration `yaml:"keep_firing_for,omitempty"`
Labels map[string]string `yaml:"labels,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty"`
}
// RuleNode adds yaml.v3 layer to support line and column outputs for invalid rules.
type RuleNode struct {
Record yaml.Node `yaml:"record,omitempty"`
Alert yaml.Node `yaml:"alert,omitempty"`
Expr yaml.Node `yaml:"expr"`
For model.Duration `yaml:"for,omitempty"`
Labels map[string]string `yaml:"labels,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty"`
Record yaml.Node `yaml:"record,omitempty"`
Alert yaml.Node `yaml:"alert,omitempty"`
Expr yaml.Node `yaml:"expr"`
For model.Duration `yaml:"for,omitempty"`
KeepFiringFor model.Duration `yaml:"keep_firing_for,omitempty"`
Labels map[string]string `yaml:"labels,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty"`
}
// Validate the rule and return a list of encountered errors.
func (r *RuleNode) Validate() (nodes []WrappedError) {
if r.Record.Value != "" && r.Alert.Value != "" {
nodes = append(nodes, WrappedError{
err: errors.Errorf("only one of 'record' and 'alert' must be set"),
err: fmt.Errorf("only one of 'record' and 'alert' must be set"),
node: &r.Record,
nodeAlt: &r.Alert,
})
@@ -142,12 +175,12 @@ func (r *RuleNode) Validate() (nodes []WrappedError) {
if r.Record.Value == "" && r.Alert.Value == "" {
if r.Record.Value == "0" {
nodes = append(nodes, WrappedError{
err: errors.Errorf("one of 'record' or 'alert' must be set"),
err: fmt.Errorf("one of 'record' or 'alert' must be set"),
node: &r.Alert,
})
} else {
nodes = append(nodes, WrappedError{
err: errors.Errorf("one of 'record' or 'alert' must be set"),
err: fmt.Errorf("one of 'record' or 'alert' must be set"),
node: &r.Record,
})
}
@@ -155,31 +188,37 @@ func (r *RuleNode) Validate() (nodes []WrappedError) {
if r.Expr.Value == "" {
nodes = append(nodes, WrappedError{
err: errors.Errorf("field 'expr' must be set in rule"),
err: fmt.Errorf("field 'expr' must be set in rule"),
node: &r.Expr,
})
} else if _, err := parser.ParseExpr(r.Expr.Value); err != nil {
nodes = append(nodes, WrappedError{
err: errors.Wrapf(err, "could not parse expression"),
err: fmt.Errorf("could not parse expression: %w", err),
node: &r.Expr,
})
}
if r.Record.Value != "" {
if len(r.Annotations) > 0 {
nodes = append(nodes, WrappedError{
err: errors.Errorf("invalid field 'annotations' in recording rule"),
err: fmt.Errorf("invalid field 'annotations' in recording rule"),
node: &r.Record,
})
}
if r.For != 0 {
nodes = append(nodes, WrappedError{
err: errors.Errorf("invalid field 'for' in recording rule"),
err: fmt.Errorf("invalid field 'for' in recording rule"),
node: &r.Record,
})
}
if r.KeepFiringFor != 0 {
nodes = append(nodes, WrappedError{
err: fmt.Errorf("invalid field 'keep_firing_for' in recording rule"),
node: &r.Record,
})
}
if !model.IsValidMetricName(model.LabelValue(r.Record.Value)) {
nodes = append(nodes, WrappedError{
err: errors.Errorf("invalid recording rule name: %s", r.Record.Value),
err: fmt.Errorf("invalid recording rule name: %s", r.Record.Value),
node: &r.Record,
})
}
@@ -188,13 +227,13 @@ func (r *RuleNode) Validate() (nodes []WrappedError) {
for k, v := range r.Labels {
if !model.LabelName(k).IsValid() || k == model.MetricNameLabel {
nodes = append(nodes, WrappedError{
err: errors.Errorf("invalid label name: %s", k),
err: fmt.Errorf("invalid label name: %s", k),
})
}
if !model.LabelValue(v).IsValid() {
nodes = append(nodes, WrappedError{
err: errors.Errorf("invalid label value: %s", v),
err: fmt.Errorf("invalid label value: %s", v),
})
}
}
@@ -202,7 +241,7 @@ func (r *RuleNode) Validate() (nodes []WrappedError) {
for k := range r.Annotations {
if !model.LabelName(k).IsValid() {
nodes = append(nodes, WrappedError{
err: errors.Errorf("invalid annotation name: %s", k),
err: fmt.Errorf("invalid annotation name: %s", k),
})
}
}
@@ -223,10 +262,11 @@ func testTemplateParsing(rl *RuleNode) (errs []error) {
}
// Trying to parse templates.
tmplData := template.AlertTemplateData(map[string]string{}, map[string]string{}, 0)
tmplData := template.AlertTemplateData(map[string]string{}, map[string]string{}, "", 0)
defs := []string{
"{{$labels := .Labels}}",
"{{$externalLabels := .ExternalLabels}}",
"{{$externalURL := .ExternalURL}}",
"{{$value := .Value}}",
}
parseTest := func(text string) error {
@@ -238,6 +278,7 @@ func testTemplateParsing(rl *RuleNode) (errs []error) {
model.Time(timestamp.FromTime(time.Now())),
nil,
nil,
nil,
)
return tmpl.ParseTest()
}
@@ -246,7 +287,7 @@ func testTemplateParsing(rl *RuleNode) (errs []error) {
for k, val := range rl.Labels {
err := parseTest(val)
if err != nil {
errs = append(errs, errors.Wrapf(err, "label %q", k))
errs = append(errs, fmt.Errorf("label %q: %w", k, err))
}
}
@@ -254,7 +295,7 @@ func testTemplateParsing(rl *RuleNode) (errs []error) {
for k, val := range rl.Annotations {
err := parseTest(val)
if err != nil {
errs = append(errs, errors.Wrapf(err, "annotation %q", k))
errs = append(errs, fmt.Errorf("annotation %q: %w", k, err))
}
}
@@ -273,7 +314,7 @@ func Parse(content []byte) (*RuleGroups, []error) {
decoder.KnownFields(true)
err := decoder.Decode(&groups)
// Ignore io.EOF which happens with empty input.
if err != nil && err != io.EOF {
if err != nil && !errors.Is(err, io.EOF) {
errs = append(errs, err)
}
err = yaml.Unmarshal(content, &node)
@@ -290,13 +331,13 @@ func Parse(content []byte) (*RuleGroups, []error) {
// ParseFile reads and parses rules from a file.
func ParseFile(file string) (*RuleGroups, []error) {
b, err := ioutil.ReadFile(file)
b, err := os.ReadFile(file)
if err != nil {
return nil, []error{errors.Wrap(err, file)}
return nil, []error{fmt.Errorf("%s: %w", file, err)}
}
rgs, errs := Parse(b)
for i := range errs {
errs[i] = errors.Wrap(errs[i], file)
errs[i] = fmt.Errorf("%s: %w", file, errs[i])
}
return rgs, errs
}

View File

@@ -16,17 +16,24 @@ package textparse
import (
"mime"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/model/exemplar"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
)
// Parser parses samples from a byte slice of samples in the official
// Prometheus and OpenMetrics text exposition formats.
type Parser interface {
// Series returns the bytes of the series, the timestamp if set, and the value
// of the current sample.
// Series returns the bytes of a series with a simple float64 as a
// value, the timestamp if set, and the value of the current sample.
Series() ([]byte, *int64, float64)
// Histogram returns the bytes of a series with a sparse histogram as a
// value, the timestamp if set, and the histogram in the current sample.
// Depending on the parsed input, the function returns an (integer) Histogram
// or a FloatHistogram, with the respective other return value being nil.
Histogram() ([]byte, *int64, *histogram.Histogram, *histogram.FloatHistogram)
// Help returns the metric name and help text in the current entry.
// Must only be called after Next returned a help entry.
// The returned byte slices become invalid after the next call to Next.
@@ -61,36 +68,51 @@ type Parser interface {
}
// New returns a new parser of the byte slice.
func New(b []byte, contentType string) Parser {
mediaType, _, err := mime.ParseMediaType(contentType)
if err == nil && mediaType == "application/openmetrics-text" {
return NewOpenMetricsParser(b)
//
// This function always returns a valid parser, but might additionally
// return an error if the content type cannot be parsed.
func New(b []byte, contentType string) (Parser, error) {
if contentType == "" {
return NewPromParser(b), nil
}
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
return NewPromParser(b), err
}
switch mediaType {
case "application/openmetrics-text":
return NewOpenMetricsParser(b), nil
case "application/vnd.google.protobuf":
return NewProtobufParser(b), nil
default:
return NewPromParser(b), nil
}
return NewPromParser(b)
}
// Entry represents the type of a parsed entry.
type Entry int
const (
EntryInvalid Entry = -1
EntryType Entry = 0
EntryHelp Entry = 1
EntrySeries Entry = 2
EntryComment Entry = 3
EntryUnit Entry = 4
EntryInvalid Entry = -1
EntryType Entry = 0
EntryHelp Entry = 1
EntrySeries Entry = 2 // A series with a simple float64 as value.
EntryComment Entry = 3
EntryUnit Entry = 4
EntryHistogram Entry = 5 // A series with a sparse histogram as a value.
)
// MetricType represents metric type values.
type MetricType string
const (
MetricTypeCounter = "counter"
MetricTypeGauge = "gauge"
MetricTypeHistogram = "histogram"
MetricTypeGaugeHistogram = "gaugehistogram"
MetricTypeSummary = "summary"
MetricTypeInfo = "info"
MetricTypeStateset = "stateset"
MetricTypeUnknown = "unknown"
MetricTypeCounter = MetricType("counter")
MetricTypeGauge = MetricType("gauge")
MetricTypeHistogram = MetricType("histogram")
MetricTypeGaugeHistogram = MetricType("gaugehistogram")
MetricTypeSummary = MetricType("summary")
MetricTypeInfo = MetricType("info")
MetricTypeStateset = MetricType("stateset")
MetricTypeUnknown = MetricType("unknown")
)

View File

@@ -58,8 +58,6 @@ yystate0:
goto yystart61
}
goto yystate0 // silence unused label error
goto yystate1 // silence unused label error
yystate1:
c = l.next()
yystart1:
@@ -94,7 +92,6 @@ yystate4:
goto yystate4
}
goto yystate5 // silence unused label error
yystate5:
c = l.next()
yystart5:
@@ -262,7 +259,6 @@ yystate24:
c = l.next()
goto yyrule4
goto yystate25 // silence unused label error
yystate25:
c = l.next()
yystart25:
@@ -282,7 +278,6 @@ yystate26:
goto yystate26
}
goto yystate27 // silence unused label error
yystate27:
c = l.next()
yystart27:
@@ -308,7 +303,6 @@ yystate29:
c = l.next()
goto yyrule7
goto yystate30 // silence unused label error
yystate30:
c = l.next()
yystart30:
@@ -346,7 +340,6 @@ yystate34:
c = l.next()
goto yyrule11
goto yystate35 // silence unused label error
yystate35:
c = l.next()
yystart35:
@@ -383,7 +376,6 @@ yystate38:
goto yystate36
}
goto yystate39 // silence unused label error
yystate39:
c = l.next()
yystart39:
@@ -418,7 +410,6 @@ yystate42:
c = l.next()
goto yyrule9
goto yystate43 // silence unused label error
yystate43:
c = l.next()
yystart43:
@@ -479,7 +470,6 @@ yystate49:
c = l.next()
goto yyrule18
goto yystate50 // silence unused label error
yystate50:
c = l.next()
yystart50:
@@ -517,7 +507,6 @@ yystate54:
c = l.next()
goto yyrule20
goto yystate55 // silence unused label error
yystate55:
c = l.next()
yystart55:
@@ -574,7 +563,6 @@ yystate60:
goto yystate58
}
goto yystate61 // silence unused label error
yystate61:
c = l.next()
yystart61:
@@ -747,16 +735,58 @@ yyrule25: // {S}[^ \n]+
return tTimestamp
}
yyrule26: // \n
{
if true { // avoid go vet determining the below panic will not be reached
l.state = sInit
return tLinebreak
goto yystate0
}
panic("unreachable")
goto yyabort // silence unused label error
yyabort: // no lexem recognized
//
// silence unused label errors for build and satisfy go vet reachability analysis
//
{
if false {
goto yyabort
}
if false {
goto yystate0
}
if false {
goto yystate1
}
if false {
goto yystate5
}
if false {
goto yystate25
}
if false {
goto yystate27
}
if false {
goto yystate30
}
if false {
goto yystate35
}
if false {
goto yystate39
}
if false {
goto yystate43
}
if false {
goto yystate50
}
if false {
goto yystate55
}
if false {
goto yystate61
}
}
return tInvalid
}

View File

@@ -18,18 +18,17 @@ package textparse
import (
"bytes"
"errors"
"fmt"
"io"
"math"
"sort"
"strings"
"unicode/utf8"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/value"
"github.com/prometheus/prometheus/model/exemplar"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/value"
)
var allowedSuffixes = [][]byte{[]byte("_total"), []byte("_bucket")}
@@ -82,6 +81,7 @@ func (l *openMetricsLexer) Error(es string) {
// This is based on the working draft https://docs.google.com/document/u/1/d/1KwV0mAXwwbvvifBvDKH_LU1YjyXE_wxCkHNoCGq1GX0/edit
type OpenMetricsParser struct {
l *openMetricsLexer
builder labels.ScratchBuilder
series []byte
text []byte
mtype MetricType
@@ -113,6 +113,12 @@ func (p *OpenMetricsParser) Series() ([]byte, *int64, float64) {
return p.series, nil, p.val
}
// Histogram returns (nil, nil, nil, nil) for now because OpenMetrics does not
// support sparse histograms yet.
func (p *OpenMetricsParser) Histogram() ([]byte, *int64, *histogram.Histogram, *histogram.FloatHistogram) {
return nil, nil, nil, nil
}
// Help returns the metric name and help text in the current entry.
// Must only be called after Next returned a help entry.
// The returned byte slices become invalid after the next call to Next.
@@ -152,14 +158,11 @@ func (p *OpenMetricsParser) Comment() []byte {
// Metric writes the labels of the current sample into the passed labels.
// It returns the string from which the metric was parsed.
func (p *OpenMetricsParser) Metric(l *labels.Labels) string {
// Allocate the full immutable string immediately, so we just
// have to create references on it below.
// Copy the buffer to a string: this is only necessary for the return value.
s := string(p.series)
*l = append(*l, labels.Label{
Name: labels.MetricName,
Value: s[:p.offsets[0]-p.start],
})
p.builder.Reset()
p.builder.Add(labels.MetricName, s[:p.offsets[0]-p.start])
for i := 1; i < len(p.offsets); i += 4 {
a := p.offsets[i] - p.start
@@ -167,17 +170,16 @@ func (p *OpenMetricsParser) Metric(l *labels.Labels) string {
c := p.offsets[i+2] - p.start
d := p.offsets[i+3] - p.start
value := s[c:d]
// Replacer causes allocations. Replace only when necessary.
if strings.IndexByte(s[c:d], byte('\\')) >= 0 {
*l = append(*l, labels.Label{Name: s[a:b], Value: lvalReplacer.Replace(s[c:d])})
continue
value = lvalReplacer.Replace(value)
}
*l = append(*l, labels.Label{Name: s[a:b], Value: s[c:d]})
p.builder.Add(s[a:b], value)
}
// Sort labels. We can skip the first entry since the metric name is
// already at the right place.
sort.Sort((*l)[1:])
p.builder.Sort()
*l = p.builder.Labels()
return s
}
@@ -199,17 +201,18 @@ func (p *OpenMetricsParser) Exemplar(e *exemplar.Exemplar) bool {
e.Ts = p.exemplarTs
}
p.builder.Reset()
for i := 0; i < len(p.eOffsets); i += 4 {
a := p.eOffsets[i] - p.start
b := p.eOffsets[i+1] - p.start
c := p.eOffsets[i+2] - p.start
d := p.eOffsets[i+3] - p.start
e.Labels = append(e.Labels, labels.Label{Name: s[a:b], Value: s[c:d]})
p.builder.Add(s[a:b], s[c:d])
}
// Sort the labels.
sort.Sort(e.Labels)
p.builder.Sort()
e.Labels = p.builder.Labels()
return true
}
@@ -241,13 +244,13 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
case tEOF:
return EntryInvalid, errors.New("data does not end with # EOF")
case tHelp, tType, tUnit:
switch t := p.nextToken(); t {
switch t2 := p.nextToken(); t2 {
case tMName:
p.offsets = append(p.offsets, p.l.start, p.l.i)
default:
return EntryInvalid, parseError("expected metric name after HELP", t)
return EntryInvalid, parseError("expected metric name after "+t.String(), t2)
}
switch t := p.nextToken(); t {
switch t2 := p.nextToken(); t2 {
case tText:
if len(p.l.buf()) > 1 {
p.text = p.l.buf()[1 : len(p.l.buf())-1]
@@ -255,7 +258,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
p.text = []byte{}
}
default:
return EntryInvalid, parseError("expected text in HELP", t)
return EntryInvalid, fmt.Errorf("expected text in %s", t.String())
}
switch t {
case tType:
@@ -277,7 +280,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
case "unknown":
p.mtype = MetricTypeUnknown
default:
return EntryInvalid, errors.Errorf("invalid metric type %q", s)
return EntryInvalid, fmt.Errorf("invalid metric type %q", s)
}
case tHelp:
if !utf8.Valid(p.text) {
@@ -294,7 +297,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
u := yoloString(p.text)
if len(u) > 0 {
if !strings.HasSuffix(m, u) || len(m) < len(u)+1 || p.l.b[p.offsets[1]-len(u)-1] != '_' {
return EntryInvalid, errors.Errorf("unit not a suffix of metric %q", m)
return EntryInvalid, fmt.Errorf("unit not a suffix of metric %q", m)
}
}
return EntryUnit, nil
@@ -306,11 +309,10 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
t2 := p.nextToken()
if t2 == tBraceOpen {
offsets, err := p.parseLVals()
p.offsets, err = p.parseLVals(p.offsets)
if err != nil {
return EntryInvalid, err
}
p.offsets = append(p.offsets, offsets...)
p.series = p.l.b[p.start:p.l.i]
t2 = p.nextToken()
}
@@ -336,6 +338,9 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
return EntryInvalid, err
}
if math.IsNaN(ts) || math.IsInf(ts, 0) {
return EntryInvalid, errors.New("invalid timestamp")
}
p.ts = int64(ts * 1000)
switch t3 := p.nextToken(); t3 {
case tLinebreak:
@@ -352,7 +357,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
return EntrySeries, nil
default:
err = errors.Errorf("%q %q is not a valid start token", t, string(p.l.cur()))
err = fmt.Errorf("%q %q is not a valid start token", t, string(p.l.cur()))
}
return EntryInvalid, err
}
@@ -364,12 +369,12 @@ func (p *OpenMetricsParser) parseComment() error {
return err
}
var err error
// Parse the labels.
offsets, err := p.parseLVals()
p.eOffsets, err = p.parseLVals(p.eOffsets)
if err != nil {
return err
}
p.eOffsets = append(p.eOffsets, offsets...)
p.exemplar = p.l.b[p.start:p.l.i]
// Get the value.
@@ -392,6 +397,9 @@ func (p *OpenMetricsParser) parseComment() error {
if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
return err
}
if math.IsNaN(ts) || math.IsInf(ts, 0) {
return errors.New("invalid exemplar timestamp")
}
p.exemplarTs = int64(ts * 1000)
switch t3 := p.nextToken(); t3 {
case tLinebreak:
@@ -404,8 +412,7 @@ func (p *OpenMetricsParser) parseComment() error {
return nil
}
func (p *OpenMetricsParser) parseLVals() ([]int, error) {
var offsets []int
func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) {
first := true
for {
t := p.nextToken()

View File

@@ -27,6 +27,9 @@ const (
sLValue
sValue
sTimestamp
sExemplar
sEValue
sETimestamp
)
// Lex is called by the parser generated by "go tool yacc" to obtain each

View File

@@ -1,4 +1,4 @@
// CAUTION: Generated file - DO NOT EDIT.
// Code generated by golex. DO NOT EDIT.
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,7 @@
package textparse
import (
"github.com/pkg/errors"
"fmt"
)
const (
@@ -47,7 +47,7 @@ yystate0:
switch yyt := l.state; yyt {
default:
panic(errors.Errorf(`invalid start condition %d`, yyt))
panic(fmt.Errorf(`invalid start condition %d`, yyt))
case 0: // start condition: INITIAL
goto yystart1
case 1: // start condition: sComment
@@ -66,8 +66,6 @@ yystate0:
goto yystart36
}
goto yystate0 // silence unused label error
goto yystate1 // silence unused label error
yystate1:
c = l.next()
yystart1:
@@ -130,7 +128,6 @@ yystate7:
goto yystate7
}
goto yystate8 // silence unused label error
yystate8:
c = l.next()
yystart8:
@@ -235,7 +232,6 @@ yystate18:
goto yystate18
}
goto yystate19 // silence unused label error
yystate19:
c = l.next()
yystart19:
@@ -257,7 +253,6 @@ yystate20:
goto yystate20
}
goto yystate21 // silence unused label error
yystate21:
c = l.next()
yystart21:
@@ -290,7 +285,6 @@ yystate23:
goto yystate22
}
goto yystate24 // silence unused label error
yystate24:
c = l.next()
yystart24:
@@ -330,7 +324,6 @@ yystate28:
c = l.next()
goto yyrule13
goto yystate29 // silence unused label error
yystate29:
c = l.next()
yystart29:
@@ -369,7 +362,6 @@ yystate32:
goto yystate30
}
goto yystate33 // silence unused label error
yystate33:
c = l.next()
yystart33:
@@ -397,7 +389,6 @@ yystate35:
c = l.next()
goto yyrule11
goto yystate36 // silence unused label error
yystate36:
c = l.next()
yystart36:
@@ -521,16 +512,50 @@ yyrule18: // {D}+
return tTimestamp
}
yyrule19: // \n
{
if true { // avoid go vet determining the below panic will not be reached
l.state = sInit
return tLinebreak
goto yystate0
}
panic("unreachable")
goto yyabort // silence unused label error
yyabort: // no lexem recognized
//
// silence unused label errors for build and satisfy go vet reachability analysis
//
{
if false {
goto yyabort
}
if false {
goto yystate0
}
if false {
goto yystate1
}
if false {
goto yystate8
}
if false {
goto yystate19
}
if false {
goto yystate21
}
if false {
goto yystate24
}
if false {
goto yystate29
}
if false {
goto yystate33
}
if false {
goto yystate36
}
}
// Workaround to gobble up comments that started with a HELP or TYPE
// prefix. We just consume all characters until we reach a newline.
// This saves us from adding disproportionate complexity to the parser.

View File

@@ -17,20 +17,19 @@
package textparse
import (
"errors"
"fmt"
"io"
"math"
"sort"
"strconv"
"strings"
"unicode/utf8"
"unsafe"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/value"
"github.com/prometheus/prometheus/model/exemplar"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/value"
)
type promlexer struct {
@@ -144,6 +143,7 @@ func (l *promlexer) Error(es string) {
// Prometheus text exposition format.
type PromParser struct {
l *promlexer
builder labels.ScratchBuilder
series []byte
text []byte
mtype MetricType
@@ -168,6 +168,12 @@ func (p *PromParser) Series() ([]byte, *int64, float64) {
return p.series, nil, p.val
}
// Histogram returns (nil, nil, nil, nil) for now because the Prometheus text
// format does not support sparse histograms yet.
func (p *PromParser) Histogram() ([]byte, *int64, *histogram.Histogram, *histogram.FloatHistogram) {
return nil, nil, nil, nil
}
// Help returns the metric name and help text in the current entry.
// Must only be called after Next returned a help entry.
// The returned byte slices become invalid after the next call to Next.
@@ -206,14 +212,11 @@ func (p *PromParser) Comment() []byte {
// Metric writes the labels of the current sample into the passed labels.
// It returns the string from which the metric was parsed.
func (p *PromParser) Metric(l *labels.Labels) string {
// Allocate the full immutable string immediately, so we just
// have to create references on it below.
// Copy the buffer to a string: this is only necessary for the return value.
s := string(p.series)
*l = append(*l, labels.Label{
Name: labels.MetricName,
Value: s[:p.offsets[0]-p.start],
})
p.builder.Reset()
p.builder.Add(labels.MetricName, s[:p.offsets[0]-p.start])
for i := 1; i < len(p.offsets); i += 4 {
a := p.offsets[i] - p.start
@@ -221,16 +224,16 @@ func (p *PromParser) Metric(l *labels.Labels) string {
c := p.offsets[i+2] - p.start
d := p.offsets[i+3] - p.start
value := s[c:d]
// Replacer causes allocations. Replace only when necessary.
if strings.IndexByte(s[c:d], byte('\\')) >= 0 {
*l = append(*l, labels.Label{Name: s[a:b], Value: lvalReplacer.Replace(s[c:d])})
continue
value = lvalReplacer.Replace(value)
}
*l = append(*l, labels.Label{Name: s[a:b], Value: s[c:d]})
p.builder.Add(s[a:b], value)
}
// Sort labels to maintain the sorted labels invariant.
sort.Sort(*l)
p.builder.Sort()
*l = p.builder.Labels()
return s
}
@@ -252,7 +255,7 @@ func (p *PromParser) nextToken() token {
}
func parseError(exp string, got token) error {
return errors.Errorf("%s, got %q", exp, got)
return fmt.Errorf("%s, got %q", exp, got)
}
// Next advances the parser to the next sample. It returns false if no
@@ -271,13 +274,13 @@ func (p *PromParser) Next() (Entry, error) {
return p.Next()
case tHelp, tType:
switch t := p.nextToken(); t {
switch t2 := p.nextToken(); t2 {
case tMName:
p.offsets = append(p.offsets, p.l.start, p.l.i)
default:
return EntryInvalid, parseError("expected metric name after HELP", t)
return EntryInvalid, parseError("expected metric name after "+t.String(), t2)
}
switch t := p.nextToken(); t {
switch t2 := p.nextToken(); t2 {
case tText:
if len(p.l.buf()) > 1 {
p.text = p.l.buf()[1:]
@@ -285,7 +288,7 @@ func (p *PromParser) Next() (Entry, error) {
p.text = []byte{}
}
default:
return EntryInvalid, parseError("expected text in HELP", t)
return EntryInvalid, fmt.Errorf("expected text in %s", t.String())
}
switch t {
case tType:
@@ -301,11 +304,11 @@ func (p *PromParser) Next() (Entry, error) {
case "untyped":
p.mtype = MetricTypeUnknown
default:
return EntryInvalid, errors.Errorf("invalid metric type %q", s)
return EntryInvalid, fmt.Errorf("invalid metric type %q", s)
}
case tHelp:
if !utf8.Valid(p.text) {
return EntryInvalid, errors.Errorf("help text is not a valid utf8 string")
return EntryInvalid, fmt.Errorf("help text is not a valid utf8 string")
}
}
if t := p.nextToken(); t != tLinebreak {
@@ -337,7 +340,7 @@ func (p *PromParser) Next() (Entry, error) {
t2 = p.nextToken()
}
if t2 != tValue {
return EntryInvalid, parseError("expected value after metric", t)
return EntryInvalid, parseError("expected value after metric", t2)
}
if p.val, err = parseFloat(yoloString(p.l.buf())); err != nil {
return EntryInvalid, err
@@ -347,7 +350,7 @@ func (p *PromParser) Next() (Entry, error) {
p.val = math.Float64frombits(value.NormalNaN)
}
p.hasTS = false
switch p.nextToken() {
switch t := p.nextToken(); t {
case tLinebreak:
break
case tTimestamp:
@@ -356,7 +359,7 @@ func (p *PromParser) Next() (Entry, error) {
return EntryInvalid, err
}
if t2 := p.nextToken(); t2 != tLinebreak {
return EntryInvalid, parseError("expected next entry after timestamp", t)
return EntryInvalid, parseError("expected next entry after timestamp", t2)
}
default:
return EntryInvalid, parseError("expected timestamp or new record", t)
@@ -364,7 +367,7 @@ func (p *PromParser) Next() (Entry, error) {
return EntrySeries, nil
default:
err = errors.Errorf("%q is not a valid start token", t)
err = fmt.Errorf("%q is not a valid start token", t)
}
return EntryInvalid, err
}
@@ -388,7 +391,7 @@ func (p *PromParser) parseLVals() error {
return parseError("expected label value", t)
}
if !utf8.Valid(p.l.buf()) {
return errors.Errorf("invalid UTF-8 label value")
return fmt.Errorf("invalid UTF-8 label value")
}
// The promlexer ensures the value string is quoted. Strip first

View File

@@ -0,0 +1,538 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package textparse
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math"
"strings"
"unicode/utf8"
"github.com/gogo/protobuf/proto"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/exemplar"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
dto "github.com/prometheus/prometheus/prompb/io/prometheus/client"
)
// ProtobufParser is a very inefficient way of unmarshaling the old Prometheus
// protobuf format and then present it as it if were parsed by a
// Prometheus-2-style text parser. This is only done so that we can easily plug
// in the protobuf format into Prometheus 2. For future use (with the final
// format that will be used for native histograms), we have to revisit the
// parsing. A lot of the efficiency tricks of the Prometheus-2-style parsing
// could be used in a similar fashion (byte-slice pointers into the raw
// payload), which requires some hand-coded protobuf handling. But the current
// parsers all expect the full series name (metric name plus label pairs) as one
// string, which is not how things are represented in the protobuf format. If
// the re-arrangement work is actually causing problems (which has to be seen),
// that expectation needs to be changed.
type ProtobufParser struct {
in []byte // The intput to parse.
inPos int // Position within the input.
metricPos int // Position within Metric slice.
// fieldPos is the position within a Summary or (legacy) Histogram. -2
// is the count. -1 is the sum. Otherwise it is the index within
// quantiles/buckets.
fieldPos int
fieldsDone bool // true if no more fields of a Summary or (legacy) Histogram to be processed.
// state is marked by the entry we are processing. EntryInvalid implies
// that we have to decode the next MetricFamily.
state Entry
builder labels.ScratchBuilder // held here to reduce allocations when building Labels
mf *dto.MetricFamily
// The following are just shenanigans to satisfy the Parser interface.
metricBytes *bytes.Buffer // A somewhat fluid representation of the current metric.
}
// NewProtobufParser returns a parser for the payload in the byte slice.
func NewProtobufParser(b []byte) Parser {
return &ProtobufParser{
in: b,
state: EntryInvalid,
mf: &dto.MetricFamily{},
metricBytes: &bytes.Buffer{},
}
}
// Series returns the bytes of a series with a simple float64 as a
// value, the timestamp if set, and the value of the current sample.
func (p *ProtobufParser) Series() ([]byte, *int64, float64) {
var (
m = p.mf.GetMetric()[p.metricPos]
ts = m.GetTimestampMs()
v float64
)
switch p.mf.GetType() {
case dto.MetricType_COUNTER:
v = m.GetCounter().GetValue()
case dto.MetricType_GAUGE:
v = m.GetGauge().GetValue()
case dto.MetricType_UNTYPED:
v = m.GetUntyped().GetValue()
case dto.MetricType_SUMMARY:
s := m.GetSummary()
switch p.fieldPos {
case -2:
v = float64(s.GetSampleCount())
case -1:
v = s.GetSampleSum()
// Need to detect a summaries without quantile here.
if len(s.GetQuantile()) == 0 {
p.fieldsDone = true
}
default:
v = s.GetQuantile()[p.fieldPos].GetValue()
}
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
// This should only happen for a legacy histogram.
h := m.GetHistogram()
switch p.fieldPos {
case -2:
v = float64(h.GetSampleCount())
case -1:
v = h.GetSampleSum()
default:
bb := h.GetBucket()
if p.fieldPos >= len(bb) {
v = float64(h.GetSampleCount())
} else {
v = float64(bb[p.fieldPos].GetCumulativeCount())
}
}
default:
panic("encountered unexpected metric type, this is a bug")
}
if ts != 0 {
return p.metricBytes.Bytes(), &ts, v
}
// Nasty hack: Assume that ts==0 means no timestamp. That's not true in
// general, but proto3 has no distinction between unset and
// default. Need to avoid in the final format.
return p.metricBytes.Bytes(), nil, v
}
// Histogram returns the bytes of a series with a native histogram as a value,
// the timestamp if set, and the native histogram in the current sample.
//
// The Compact method is called before returning the Histogram (or FloatHistogram).
//
// If the SampleCountFloat or the ZeroCountFloat in the proto message is > 0,
// the histogram is parsed and returned as a FloatHistogram and nil is returned
// as the (integer) Histogram return value. Otherwise, it is parsed and returned
// as an (integer) Histogram and nil is returned as the FloatHistogram return
// value.
func (p *ProtobufParser) Histogram() ([]byte, *int64, *histogram.Histogram, *histogram.FloatHistogram) {
var (
m = p.mf.GetMetric()[p.metricPos]
ts = m.GetTimestampMs()
h = m.GetHistogram()
)
if h.GetSampleCountFloat() > 0 || h.GetZeroCountFloat() > 0 {
// It is a float histogram.
fh := histogram.FloatHistogram{
Count: h.GetSampleCountFloat(),
Sum: h.GetSampleSum(),
ZeroThreshold: h.GetZeroThreshold(),
ZeroCount: h.GetZeroCountFloat(),
Schema: h.GetSchema(),
PositiveSpans: make([]histogram.Span, len(h.GetPositiveSpan())),
PositiveBuckets: h.GetPositiveCount(),
NegativeSpans: make([]histogram.Span, len(h.GetNegativeSpan())),
NegativeBuckets: h.GetNegativeCount(),
}
for i, span := range h.GetPositiveSpan() {
fh.PositiveSpans[i].Offset = span.GetOffset()
fh.PositiveSpans[i].Length = span.GetLength()
}
for i, span := range h.GetNegativeSpan() {
fh.NegativeSpans[i].Offset = span.GetOffset()
fh.NegativeSpans[i].Length = span.GetLength()
}
if p.mf.GetType() == dto.MetricType_GAUGE_HISTOGRAM {
fh.CounterResetHint = histogram.GaugeType
}
fh.Compact(0)
if ts != 0 {
return p.metricBytes.Bytes(), &ts, nil, &fh
}
// Nasty hack: Assume that ts==0 means no timestamp. That's not true in
// general, but proto3 has no distinction between unset and
// default. Need to avoid in the final format.
return p.metricBytes.Bytes(), nil, nil, &fh
}
sh := histogram.Histogram{
Count: h.GetSampleCount(),
Sum: h.GetSampleSum(),
ZeroThreshold: h.GetZeroThreshold(),
ZeroCount: h.GetZeroCount(),
Schema: h.GetSchema(),
PositiveSpans: make([]histogram.Span, len(h.GetPositiveSpan())),
PositiveBuckets: h.GetPositiveDelta(),
NegativeSpans: make([]histogram.Span, len(h.GetNegativeSpan())),
NegativeBuckets: h.GetNegativeDelta(),
}
for i, span := range h.GetPositiveSpan() {
sh.PositiveSpans[i].Offset = span.GetOffset()
sh.PositiveSpans[i].Length = span.GetLength()
}
for i, span := range h.GetNegativeSpan() {
sh.NegativeSpans[i].Offset = span.GetOffset()
sh.NegativeSpans[i].Length = span.GetLength()
}
if p.mf.GetType() == dto.MetricType_GAUGE_HISTOGRAM {
sh.CounterResetHint = histogram.GaugeType
}
sh.Compact(0)
if ts != 0 {
return p.metricBytes.Bytes(), &ts, &sh, nil
}
return p.metricBytes.Bytes(), nil, &sh, nil
}
// Help returns the metric name and help text in the current entry.
// Must only be called after Next returned a help entry.
// The returned byte slices become invalid after the next call to Next.
func (p *ProtobufParser) Help() ([]byte, []byte) {
return p.metricBytes.Bytes(), []byte(p.mf.GetHelp())
}
// Type returns the metric name and type in the current entry.
// Must only be called after Next returned a type entry.
// The returned byte slices become invalid after the next call to Next.
func (p *ProtobufParser) Type() ([]byte, MetricType) {
n := p.metricBytes.Bytes()
switch p.mf.GetType() {
case dto.MetricType_COUNTER:
return n, MetricTypeCounter
case dto.MetricType_GAUGE:
return n, MetricTypeGauge
case dto.MetricType_HISTOGRAM:
return n, MetricTypeHistogram
case dto.MetricType_GAUGE_HISTOGRAM:
return n, MetricTypeGaugeHistogram
case dto.MetricType_SUMMARY:
return n, MetricTypeSummary
}
return n, MetricTypeUnknown
}
// Unit always returns (nil, nil) because units aren't supported by the protobuf
// format.
func (p *ProtobufParser) Unit() ([]byte, []byte) {
return nil, nil
}
// Comment always returns nil because comments aren't supported by the protobuf
// format.
func (p *ProtobufParser) Comment() []byte {
return nil
}
// Metric writes the labels of the current sample into the passed labels.
// It returns the string from which the metric was parsed.
func (p *ProtobufParser) Metric(l *labels.Labels) string {
p.builder.Reset()
p.builder.Add(labels.MetricName, p.getMagicName())
for _, lp := range p.mf.GetMetric()[p.metricPos].GetLabel() {
p.builder.Add(lp.GetName(), lp.GetValue())
}
if needed, name, value := p.getMagicLabel(); needed {
p.builder.Add(name, value)
}
// Sort labels to maintain the sorted labels invariant.
p.builder.Sort()
*l = p.builder.Labels()
return p.metricBytes.String()
}
// Exemplar writes the exemplar of the current sample into the passed
// exemplar. It returns if an exemplar exists or not. In case of a native
// histogram, the legacy bucket section is still used for exemplars. To ingest
// all examplars, call the Exemplar method repeatedly until it returns false.
func (p *ProtobufParser) Exemplar(ex *exemplar.Exemplar) bool {
m := p.mf.GetMetric()[p.metricPos]
var exProto *dto.Exemplar
switch p.mf.GetType() {
case dto.MetricType_COUNTER:
exProto = m.GetCounter().GetExemplar()
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
bb := m.GetHistogram().GetBucket()
if p.fieldPos < 0 {
if p.state == EntrySeries {
return false // At _count or _sum.
}
p.fieldPos = 0 // Start at 1st bucket for native histograms.
}
for p.fieldPos < len(bb) {
exProto = bb[p.fieldPos].GetExemplar()
if p.state == EntrySeries {
break
}
p.fieldPos++
if exProto != nil {
break
}
}
default:
return false
}
if exProto == nil {
return false
}
ex.Value = exProto.GetValue()
if ts := exProto.GetTimestamp(); ts != nil {
ex.HasTs = true
ex.Ts = ts.GetSeconds()*1000 + int64(ts.GetNanos()/1_000_000)
}
p.builder.Reset()
for _, lp := range exProto.GetLabel() {
p.builder.Add(lp.GetName(), lp.GetValue())
}
p.builder.Sort()
ex.Labels = p.builder.Labels()
return true
}
// Next advances the parser to the next "sample" (emulating the behavior of a
// text format parser). It returns (EntryInvalid, io.EOF) if no samples were
// read.
func (p *ProtobufParser) Next() (Entry, error) {
switch p.state {
case EntryInvalid:
p.metricPos = 0
p.fieldPos = -2
n, err := readDelimited(p.in[p.inPos:], p.mf)
p.inPos += n
if err != nil {
return p.state, err
}
// Skip empty metric families.
if len(p.mf.GetMetric()) == 0 {
return p.Next()
}
// We are at the beginning of a metric family. Put only the name
// into metricBytes and validate only name, help, and type for now.
name := p.mf.GetName()
if !model.IsValidMetricName(model.LabelValue(name)) {
return EntryInvalid, errors.Errorf("invalid metric name: %s", name)
}
if help := p.mf.GetHelp(); !utf8.ValidString(help) {
return EntryInvalid, errors.Errorf("invalid help for metric %q: %s", name, help)
}
switch p.mf.GetType() {
case dto.MetricType_COUNTER,
dto.MetricType_GAUGE,
dto.MetricType_HISTOGRAM,
dto.MetricType_GAUGE_HISTOGRAM,
dto.MetricType_SUMMARY,
dto.MetricType_UNTYPED:
// All good.
default:
return EntryInvalid, errors.Errorf("unknown metric type for metric %q: %s", name, p.mf.GetType())
}
p.metricBytes.Reset()
p.metricBytes.WriteString(name)
p.state = EntryHelp
case EntryHelp:
p.state = EntryType
case EntryType:
t := p.mf.GetType()
if (t == dto.MetricType_HISTOGRAM || t == dto.MetricType_GAUGE_HISTOGRAM) &&
isNativeHistogram(p.mf.GetMetric()[0].GetHistogram()) {
p.state = EntryHistogram
} else {
p.state = EntrySeries
}
if err := p.updateMetricBytes(); err != nil {
return EntryInvalid, err
}
case EntryHistogram, EntrySeries:
t := p.mf.GetType()
if p.state == EntrySeries && !p.fieldsDone &&
(t == dto.MetricType_SUMMARY ||
t == dto.MetricType_HISTOGRAM ||
t == dto.MetricType_GAUGE_HISTOGRAM) {
p.fieldPos++
} else {
p.metricPos++
p.fieldPos = -2
p.fieldsDone = false
}
if p.metricPos >= len(p.mf.GetMetric()) {
p.state = EntryInvalid
return p.Next()
}
if err := p.updateMetricBytes(); err != nil {
return EntryInvalid, err
}
default:
return EntryInvalid, errors.Errorf("invalid protobuf parsing state: %d", p.state)
}
return p.state, nil
}
func (p *ProtobufParser) updateMetricBytes() error {
b := p.metricBytes
b.Reset()
b.WriteString(p.getMagicName())
for _, lp := range p.mf.GetMetric()[p.metricPos].GetLabel() {
b.WriteByte(model.SeparatorByte)
n := lp.GetName()
if !model.LabelName(n).IsValid() {
return errors.Errorf("invalid label name: %s", n)
}
b.WriteString(n)
b.WriteByte(model.SeparatorByte)
v := lp.GetValue()
if !utf8.ValidString(v) {
return errors.Errorf("invalid label value: %s", v)
}
b.WriteString(v)
}
if needed, n, v := p.getMagicLabel(); needed {
b.WriteByte(model.SeparatorByte)
b.WriteString(n)
b.WriteByte(model.SeparatorByte)
b.WriteString(v)
}
return nil
}
// getMagicName usually just returns p.mf.GetType() but adds a magic suffix
// ("_count", "_sum", "_bucket") if needed according to the current parser
// state.
func (p *ProtobufParser) getMagicName() string {
t := p.mf.GetType()
if p.state == EntryHistogram || (t != dto.MetricType_HISTOGRAM && t != dto.MetricType_SUMMARY) {
return p.mf.GetName()
}
if p.fieldPos == -2 {
return p.mf.GetName() + "_count"
}
if p.fieldPos == -1 {
return p.mf.GetName() + "_sum"
}
if t == dto.MetricType_HISTOGRAM || t == dto.MetricType_GAUGE_HISTOGRAM {
return p.mf.GetName() + "_bucket"
}
return p.mf.GetName()
}
// getMagicLabel returns if a magic label ("quantile" or "le") is needed and, if
// so, its name and value. It also sets p.fieldsDone if applicable.
func (p *ProtobufParser) getMagicLabel() (bool, string, string) {
if p.state == EntryHistogram || p.fieldPos < 0 {
return false, "", ""
}
switch p.mf.GetType() {
case dto.MetricType_SUMMARY:
qq := p.mf.GetMetric()[p.metricPos].GetSummary().GetQuantile()
q := qq[p.fieldPos]
p.fieldsDone = p.fieldPos == len(qq)-1
return true, model.QuantileLabel, formatOpenMetricsFloat(q.GetQuantile())
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
bb := p.mf.GetMetric()[p.metricPos].GetHistogram().GetBucket()
if p.fieldPos >= len(bb) {
p.fieldsDone = true
return true, model.BucketLabel, "+Inf"
}
b := bb[p.fieldPos]
p.fieldsDone = math.IsInf(b.GetUpperBound(), +1)
return true, model.BucketLabel, formatOpenMetricsFloat(b.GetUpperBound())
}
return false, "", ""
}
var errInvalidVarint = errors.New("protobufparse: invalid varint encountered")
// readDelimited is essentially doing what the function of the same name in
// github.com/matttproud/golang_protobuf_extensions/pbutil is doing, but it is
// specific to a MetricFamily, utilizes the more efficient gogo-protobuf
// unmarshaling, and acts on a byte slice directly without any additional
// staging buffers.
func readDelimited(b []byte, mf *dto.MetricFamily) (n int, err error) {
if len(b) == 0 {
return 0, io.EOF
}
messageLength, varIntLength := proto.DecodeVarint(b)
if varIntLength == 0 || varIntLength > binary.MaxVarintLen32 {
return 0, errInvalidVarint
}
totalLength := varIntLength + int(messageLength)
if totalLength > len(b) {
return 0, errors.Errorf("protobufparse: insufficient length of buffer, expected at least %d bytes, got %d bytes", totalLength, len(b))
}
mf.Reset()
return totalLength, mf.Unmarshal(b[varIntLength:totalLength])
}
// formatOpenMetricsFloat works like the usual Go string formatting of a fleat
// but appends ".0" if the resulting number would otherwise contain neither a
// "." nor an "e".
func formatOpenMetricsFloat(f float64) string {
// A few common cases hardcoded.
switch {
case f == 1:
return "1.0"
case f == 0:
return "0.0"
case f == -1:
return "-1.0"
case math.IsNaN(f):
return "NaN"
case math.IsInf(f, +1):
return "+Inf"
case math.IsInf(f, -1):
return "-Inf"
}
s := fmt.Sprint(f)
if strings.ContainsAny(s, "e.") {
return s
}
return s + ".0"
}
// isNativeHistogram returns false iff the provided histograms has no sparse
// buckets and a zero threshold of 0 and a zero count of 0. In principle, this
// could still be meant to be a native histogram (with a zero threshold of 0 and
// no observations yet), but for now, we'll treat this case as a conventional
// histogram.
//
// TODO(beorn7): In the final format, there should be an unambiguous way of
// deciding if a histogram should be ingested as a conventional one or a native
// one.
func isNativeHistogram(h *dto.Histogram) bool {
return len(h.GetNegativeDelta()) > 0 ||
len(h.GetPositiveDelta()) > 0 ||
h.GetZeroCount() > 0 ||
h.GetZeroThreshold() > 0
}

View File

@@ -13,7 +13,10 @@
package timestamp
import "time"
import (
"math"
"time"
)
// FromTime returns a new millisecond timestamp from a time.
func FromTime(t time.Time) int64 {
@@ -24,3 +27,8 @@ func FromTime(t time.Time) int64 {
func Time(ts int64) time.Time {
return time.Unix(ts/1000, (ts%1000)*int64(time.Millisecond)).UTC()
}
// FromFloatSeconds returns a millisecond timestamp from float seconds.
func FromFloatSeconds(ts float64) int64 {
return int64(math.Round(ts * 1000))
}

View File

@@ -0,0 +1,736 @@
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notifier
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path"
"sync"
"time"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/go-openapi/strfmt"
"github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/client_golang/prometheus"
config_util "github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
"go.uber.org/atomic"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/relabel"
)
const (
contentTypeJSON = "application/json"
)
// String constants for instrumentation.
const (
namespace = "prometheus"
subsystem = "notifications"
alertmanagerLabel = "alertmanager"
)
var userAgent = fmt.Sprintf("Prometheus/%s", version.Version)
// Alert is a generic representation of an alert in the Prometheus eco-system.
type Alert struct {
// Label value pairs for purpose of aggregation, matching, and disposition
// dispatching. This must minimally include an "alertname" label.
Labels labels.Labels `json:"labels"`
// Extra key/value information which does not define alert identity.
Annotations labels.Labels `json:"annotations"`
// The known time range for this alert. Both ends are optional.
StartsAt time.Time `json:"startsAt,omitempty"`
EndsAt time.Time `json:"endsAt,omitempty"`
GeneratorURL string `json:"generatorURL,omitempty"`
}
// Name returns the name of the alert. It is equivalent to the "alertname" label.
func (a *Alert) Name() string {
return a.Labels.Get(labels.AlertName)
}
// Hash returns a hash over the alert. It is equivalent to the alert labels hash.
func (a *Alert) Hash() uint64 {
return a.Labels.Hash()
}
func (a *Alert) String() string {
s := fmt.Sprintf("%s[%s]", a.Name(), fmt.Sprintf("%016x", a.Hash())[:7])
if a.Resolved() {
return s + "[resolved]"
}
return s + "[active]"
}
// Resolved returns true iff the activity interval ended in the past.
func (a *Alert) Resolved() bool {
return a.ResolvedAt(time.Now())
}
// ResolvedAt returns true iff the activity interval ended before
// the given timestamp.
func (a *Alert) ResolvedAt(ts time.Time) bool {
if a.EndsAt.IsZero() {
return false
}
return !a.EndsAt.After(ts)
}
// Manager is responsible for dispatching alert notifications to an
// alert manager service.
type Manager struct {
queue []*Alert
opts *Options
metrics *alertMetrics
more chan struct{}
mtx sync.RWMutex
ctx context.Context
cancel func()
alertmanagers map[string]*alertmanagerSet
logger log.Logger
}
// Options are the configurable parameters of a Handler.
type Options struct {
QueueCapacity int
ExternalLabels labels.Labels
RelabelConfigs []*relabel.Config
// Used for sending HTTP requests to the Alertmanager.
Do func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error)
Registerer prometheus.Registerer
}
type alertMetrics struct {
latency *prometheus.SummaryVec
errors *prometheus.CounterVec
sent *prometheus.CounterVec
dropped prometheus.Counter
queueLength prometheus.GaugeFunc
queueCapacity prometheus.Gauge
alertmanagersDiscovered prometheus.GaugeFunc
}
func newAlertMetrics(r prometheus.Registerer, queueCap int, queueLen, alertmanagersDiscovered func() float64) *alertMetrics {
m := &alertMetrics{
latency: prometheus.NewSummaryVec(prometheus.SummaryOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "latency_seconds",
Help: "Latency quantiles for sending alert notifications.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{alertmanagerLabel},
),
errors: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "errors_total",
Help: "Total number of errors sending alert notifications.",
},
[]string{alertmanagerLabel},
),
sent: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "sent_total",
Help: "Total number of alerts sent.",
},
[]string{alertmanagerLabel},
),
dropped: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "dropped_total",
Help: "Total number of alerts dropped due to errors when sending to Alertmanager.",
}),
queueLength: prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "queue_length",
Help: "The number of alert notifications in the queue.",
}, queueLen),
queueCapacity: prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "queue_capacity",
Help: "The capacity of the alert notifications queue.",
}),
alertmanagersDiscovered: prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "prometheus_notifications_alertmanagers_discovered",
Help: "The number of alertmanagers discovered and active.",
}, alertmanagersDiscovered),
}
m.queueCapacity.Set(float64(queueCap))
if r != nil {
r.MustRegister(
m.latency,
m.errors,
m.sent,
m.dropped,
m.queueLength,
m.queueCapacity,
m.alertmanagersDiscovered,
)
}
return m
}
func do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
}
return client.Do(req.WithContext(ctx))
}
// NewManager is the manager constructor.
func NewManager(o *Options, logger log.Logger) *Manager {
ctx, cancel := context.WithCancel(context.Background())
if o.Do == nil {
o.Do = do
}
if logger == nil {
logger = log.NewNopLogger()
}
n := &Manager{
queue: make([]*Alert, 0, o.QueueCapacity),
ctx: ctx,
cancel: cancel,
more: make(chan struct{}, 1),
opts: o,
logger: logger,
}
queueLenFunc := func() float64 { return float64(n.queueLen()) }
alertmanagersDiscoveredFunc := func() float64 { return float64(len(n.Alertmanagers())) }
n.metrics = newAlertMetrics(
o.Registerer,
o.QueueCapacity,
queueLenFunc,
alertmanagersDiscoveredFunc,
)
return n
}
// ApplyConfig updates the status state as the new config requires.
func (n *Manager) ApplyConfig(conf *config.Config) error {
n.mtx.Lock()
defer n.mtx.Unlock()
n.opts.ExternalLabels = conf.GlobalConfig.ExternalLabels
n.opts.RelabelConfigs = conf.AlertingConfig.AlertRelabelConfigs
amSets := make(map[string]*alertmanagerSet)
for k, cfg := range conf.AlertingConfig.AlertmanagerConfigs.ToMap() {
ams, err := newAlertmanagerSet(cfg, n.logger, n.metrics)
if err != nil {
return err
}
amSets[k] = ams
}
n.alertmanagers = amSets
return nil
}
const maxBatchSize = 64
func (n *Manager) queueLen() int {
n.mtx.RLock()
defer n.mtx.RUnlock()
return len(n.queue)
}
func (n *Manager) nextBatch() []*Alert {
n.mtx.Lock()
defer n.mtx.Unlock()
var alerts []*Alert
if len(n.queue) > maxBatchSize {
alerts = append(make([]*Alert, 0, maxBatchSize), n.queue[:maxBatchSize]...)
n.queue = n.queue[maxBatchSize:]
} else {
alerts = append(make([]*Alert, 0, len(n.queue)), n.queue...)
n.queue = n.queue[:0]
}
return alerts
}
// Run dispatches notifications continuously.
func (n *Manager) Run(tsets <-chan map[string][]*targetgroup.Group) {
for {
// The select is split in two parts, such as we will first try to read
// new alertmanager targets if they are available, before sending new
// alerts.
select {
case <-n.ctx.Done():
return
case ts := <-tsets:
n.reload(ts)
default:
select {
case <-n.ctx.Done():
return
case ts := <-tsets:
n.reload(ts)
case <-n.more:
}
}
alerts := n.nextBatch()
if !n.sendAll(alerts...) {
n.metrics.dropped.Add(float64(len(alerts)))
}
// If the queue still has items left, kick off the next iteration.
if n.queueLen() > 0 {
n.setMore()
}
}
}
func (n *Manager) reload(tgs map[string][]*targetgroup.Group) {
n.mtx.Lock()
defer n.mtx.Unlock()
for id, tgroup := range tgs {
am, ok := n.alertmanagers[id]
if !ok {
level.Error(n.logger).Log("msg", "couldn't sync alert manager set", "err", fmt.Sprintf("invalid id:%v", id))
continue
}
am.sync(tgroup)
}
}
// Send queues the given notification requests for processing.
// Panics if called on a handler that is not running.
func (n *Manager) Send(alerts ...*Alert) {
n.mtx.Lock()
defer n.mtx.Unlock()
// Attach external labels before relabelling and sending.
for _, a := range alerts {
lb := labels.NewBuilder(a.Labels)
n.opts.ExternalLabels.Range(func(l labels.Label) {
if a.Labels.Get(l.Name) == "" {
lb.Set(l.Name, l.Value)
}
})
a.Labels = lb.Labels(a.Labels)
}
alerts = n.relabelAlerts(alerts)
if len(alerts) == 0 {
return
}
// Queue capacity should be significantly larger than a single alert
// batch could be.
if d := len(alerts) - n.opts.QueueCapacity; d > 0 {
alerts = alerts[d:]
level.Warn(n.logger).Log("msg", "Alert batch larger than queue capacity, dropping alerts", "num_dropped", d)
n.metrics.dropped.Add(float64(d))
}
// If the queue is full, remove the oldest alerts in favor
// of newer ones.
if d := (len(n.queue) + len(alerts)) - n.opts.QueueCapacity; d > 0 {
n.queue = n.queue[d:]
level.Warn(n.logger).Log("msg", "Alert notification queue full, dropping alerts", "num_dropped", d)
n.metrics.dropped.Add(float64(d))
}
n.queue = append(n.queue, alerts...)
// Notify sending goroutine that there are alerts to be processed.
n.setMore()
}
func (n *Manager) relabelAlerts(alerts []*Alert) []*Alert {
var relabeledAlerts []*Alert
for _, alert := range alerts {
labels, keep := relabel.Process(alert.Labels, n.opts.RelabelConfigs...)
if keep {
alert.Labels = labels
relabeledAlerts = append(relabeledAlerts, alert)
}
}
return relabeledAlerts
}
// setMore signals that the alert queue has items.
func (n *Manager) setMore() {
// If we cannot send on the channel, it means the signal already exists
// and has not been consumed yet.
select {
case n.more <- struct{}{}:
default:
}
}
// Alertmanagers returns a slice of Alertmanager URLs.
func (n *Manager) Alertmanagers() []*url.URL {
n.mtx.RLock()
amSets := n.alertmanagers
n.mtx.RUnlock()
var res []*url.URL
for _, ams := range amSets {
ams.mtx.RLock()
for _, am := range ams.ams {
res = append(res, am.url())
}
ams.mtx.RUnlock()
}
return res
}
// DroppedAlertmanagers returns a slice of Alertmanager URLs.
func (n *Manager) DroppedAlertmanagers() []*url.URL {
n.mtx.RLock()
amSets := n.alertmanagers
n.mtx.RUnlock()
var res []*url.URL
for _, ams := range amSets {
ams.mtx.RLock()
for _, dam := range ams.droppedAms {
res = append(res, dam.url())
}
ams.mtx.RUnlock()
}
return res
}
// sendAll sends the alerts to all configured Alertmanagers concurrently.
// It returns true if the alerts could be sent successfully to at least one Alertmanager.
func (n *Manager) sendAll(alerts ...*Alert) bool {
if len(alerts) == 0 {
return true
}
begin := time.Now()
// v1Payload and v2Payload represent 'alerts' marshaled for Alertmanager API
// v1 or v2. Marshaling happens below. Reference here is for caching between
// for loop iterations.
var v1Payload, v2Payload []byte
n.mtx.RLock()
amSets := n.alertmanagers
n.mtx.RUnlock()
var (
wg sync.WaitGroup
numSuccess atomic.Uint64
)
for _, ams := range amSets {
var (
payload []byte
err error
)
ams.mtx.RLock()
switch ams.cfg.APIVersion {
case config.AlertmanagerAPIVersionV1:
{
if v1Payload == nil {
v1Payload, err = json.Marshal(alerts)
if err != nil {
level.Error(n.logger).Log("msg", "Encoding alerts for Alertmanager API v1 failed", "err", err)
ams.mtx.RUnlock()
return false
}
}
payload = v1Payload
}
case config.AlertmanagerAPIVersionV2:
{
if v2Payload == nil {
openAPIAlerts := alertsToOpenAPIAlerts(alerts)
v2Payload, err = json.Marshal(openAPIAlerts)
if err != nil {
level.Error(n.logger).Log("msg", "Encoding alerts for Alertmanager API v2 failed", "err", err)
ams.mtx.RUnlock()
return false
}
}
payload = v2Payload
}
default:
{
level.Error(n.logger).Log(
"msg", fmt.Sprintf("Invalid Alertmanager API version '%v', expected one of '%v'", ams.cfg.APIVersion, config.SupportedAlertmanagerAPIVersions),
"err", err,
)
ams.mtx.RUnlock()
return false
}
}
for _, am := range ams.ams {
wg.Add(1)
ctx, cancel := context.WithTimeout(n.ctx, time.Duration(ams.cfg.Timeout))
defer cancel()
go func(client *http.Client, url string) {
if err := n.sendOne(ctx, client, url, payload); err != nil {
level.Error(n.logger).Log("alertmanager", url, "count", len(alerts), "msg", "Error sending alert", "err", err)
n.metrics.errors.WithLabelValues(url).Inc()
} else {
numSuccess.Inc()
}
n.metrics.latency.WithLabelValues(url).Observe(time.Since(begin).Seconds())
n.metrics.sent.WithLabelValues(url).Add(float64(len(alerts)))
wg.Done()
}(ams.client, am.url().String())
}
ams.mtx.RUnlock()
}
wg.Wait()
return numSuccess.Load() > 0
}
func alertsToOpenAPIAlerts(alerts []*Alert) models.PostableAlerts {
openAPIAlerts := models.PostableAlerts{}
for _, a := range alerts {
start := strfmt.DateTime(a.StartsAt)
end := strfmt.DateTime(a.EndsAt)
openAPIAlerts = append(openAPIAlerts, &models.PostableAlert{
Annotations: labelsToOpenAPILabelSet(a.Annotations),
EndsAt: end,
StartsAt: start,
Alert: models.Alert{
GeneratorURL: strfmt.URI(a.GeneratorURL),
Labels: labelsToOpenAPILabelSet(a.Labels),
},
})
}
return openAPIAlerts
}
func labelsToOpenAPILabelSet(modelLabelSet labels.Labels) models.LabelSet {
apiLabelSet := models.LabelSet{}
modelLabelSet.Range(func(label labels.Label) {
apiLabelSet[label.Name] = label.Value
})
return apiLabelSet
}
func (n *Manager) sendOne(ctx context.Context, c *http.Client, url string, b []byte) error {
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
if err != nil {
return err
}
req.Header.Set("User-Agent", userAgent)
req.Header.Set("Content-Type", contentTypeJSON)
resp, err := n.opts.Do(ctx, c, req)
if err != nil {
return err
}
defer func() {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
// Any HTTP status 2xx is OK.
if resp.StatusCode/100 != 2 {
return fmt.Errorf("bad response status %s", resp.Status)
}
return nil
}
// Stop shuts down the notification handler.
func (n *Manager) Stop() {
level.Info(n.logger).Log("msg", "Stopping notification manager...")
n.cancel()
}
// Alertmanager holds Alertmanager endpoint information.
type alertmanager interface {
url() *url.URL
}
type alertmanagerLabels struct{ labels.Labels }
const pathLabel = "__alerts_path__"
func (a alertmanagerLabels) url() *url.URL {
return &url.URL{
Scheme: a.Get(model.SchemeLabel),
Host: a.Get(model.AddressLabel),
Path: a.Get(pathLabel),
}
}
// alertmanagerSet contains a set of Alertmanagers discovered via a group of service
// discovery definitions that have a common configuration on how alerts should be sent.
type alertmanagerSet struct {
cfg *config.AlertmanagerConfig
client *http.Client
metrics *alertMetrics
mtx sync.RWMutex
ams []alertmanager
droppedAms []alertmanager
logger log.Logger
}
func newAlertmanagerSet(cfg *config.AlertmanagerConfig, logger log.Logger, metrics *alertMetrics) (*alertmanagerSet, error) {
client, err := config_util.NewClientFromConfig(cfg.HTTPClientConfig, "alertmanager")
if err != nil {
return nil, err
}
s := &alertmanagerSet{
client: client,
cfg: cfg,
logger: logger,
metrics: metrics,
}
return s, nil
}
// sync extracts a deduplicated set of Alertmanager endpoints from a list
// of target groups definitions.
func (s *alertmanagerSet) sync(tgs []*targetgroup.Group) {
allAms := []alertmanager{}
allDroppedAms := []alertmanager{}
for _, tg := range tgs {
ams, droppedAms, err := AlertmanagerFromGroup(tg, s.cfg)
if err != nil {
level.Error(s.logger).Log("msg", "Creating discovered Alertmanagers failed", "err", err)
continue
}
allAms = append(allAms, ams...)
allDroppedAms = append(allDroppedAms, droppedAms...)
}
s.mtx.Lock()
defer s.mtx.Unlock()
// Set new Alertmanagers and deduplicate them along their unique URL.
s.ams = []alertmanager{}
s.droppedAms = []alertmanager{}
s.droppedAms = append(s.droppedAms, allDroppedAms...)
seen := map[string]struct{}{}
for _, am := range allAms {
us := am.url().String()
if _, ok := seen[us]; ok {
continue
}
// This will initialize the Counters for the AM to 0.
s.metrics.sent.WithLabelValues(us)
s.metrics.errors.WithLabelValues(us)
seen[us] = struct{}{}
s.ams = append(s.ams, am)
}
}
func postPath(pre string, v config.AlertmanagerAPIVersion) string {
alertPushEndpoint := fmt.Sprintf("/api/%v/alerts", string(v))
return path.Join("/", pre, alertPushEndpoint)
}
// AlertmanagerFromGroup extracts a list of alertmanagers from a target group
// and an associated AlertmanagerConfig.
func AlertmanagerFromGroup(tg *targetgroup.Group, cfg *config.AlertmanagerConfig) ([]alertmanager, []alertmanager, error) {
var res []alertmanager
var droppedAlertManagers []alertmanager
for _, tlset := range tg.Targets {
lbls := make([]labels.Label, 0, len(tlset)+2+len(tg.Labels))
for ln, lv := range tlset {
lbls = append(lbls, labels.Label{Name: string(ln), Value: string(lv)})
}
// Set configured scheme as the initial scheme label for overwrite.
lbls = append(lbls, labels.Label{Name: model.SchemeLabel, Value: cfg.Scheme})
lbls = append(lbls, labels.Label{Name: pathLabel, Value: postPath(cfg.PathPrefix, cfg.APIVersion)})
// Combine target labels with target group labels.
for ln, lv := range tg.Labels {
if _, ok := tlset[ln]; !ok {
lbls = append(lbls, labels.Label{Name: string(ln), Value: string(lv)})
}
}
lset, keep := relabel.Process(labels.New(lbls...), cfg.RelabelConfigs...)
if !keep {
droppedAlertManagers = append(droppedAlertManagers, alertmanagerLabels{labels.New(lbls...)})
continue
}
addr := lset.Get(model.AddressLabel)
if err := config.CheckTargetAddress(model.LabelValue(addr)); err != nil {
return nil, nil, err
}
res = append(res, alertmanagerLabels{lset})
}
return res, droppedAlertManagers, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
// Copyright 2013 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This is copied and lightly edited from
// github.com/prometheus/client_model/io/prometheus/client/metrics.proto
// and finally converted to proto3 syntax to make it usable for the
// gogo-protobuf approach taken within prometheus/prometheus.
syntax = "proto3";
package io.prometheus.client;
option go_package = "io_prometheus_client";
import "google/protobuf/timestamp.proto";
message LabelPair {
string name = 1;
string value = 2;
}
enum MetricType {
// COUNTER must use the Metric field "counter".
COUNTER = 0;
// GAUGE must use the Metric field "gauge".
GAUGE = 1;
// SUMMARY must use the Metric field "summary".
SUMMARY = 2;
// UNTYPED must use the Metric field "untyped".
UNTYPED = 3;
// HISTOGRAM must use the Metric field "histogram".
HISTOGRAM = 4;
// GAUGE_HISTOGRAM must use the Metric field "histogram".
GAUGE_HISTOGRAM = 5;
}
message Gauge {
double value = 1;
}
message Counter {
double value = 1;
Exemplar exemplar = 2;
}
message Quantile {
double quantile = 1;
double value = 2;
}
message Summary {
uint64 sample_count = 1;
double sample_sum = 2;
repeated Quantile quantile = 3;
}
message Untyped {
double value = 1;
}
message Histogram {
uint64 sample_count = 1;
double sample_count_float = 4; // Overrides sample_count if > 0.
double sample_sum = 2;
// Buckets for the conventional histogram.
repeated Bucket bucket = 3; // Ordered in increasing order of upper_bound, +Inf bucket is optional.
// Everything below here is for native histograms (also known as sparse histograms).
// Native histograms are an experimental feature without stability guarantees.
// schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8.
// They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and
// then each power of two is divided into 2^n logarithmic buckets.
// Or in other words, each bucket boundary is the previous boundary times 2^(2^-n).
// In the future, more bucket schemas may be added using numbers < -4 or > 8.
sint32 schema = 5;
double zero_threshold = 6; // Breadth of the zero bucket.
uint64 zero_count = 7; // Count in zero bucket.
double zero_count_float = 8; // Overrides sb_zero_count if > 0.
// Negative buckets for the native histogram.
repeated BucketSpan negative_span = 9;
// Use either "negative_delta" or "negative_count", the former for
// regular histograms with integer counts, the latter for float
// histograms.
repeated sint64 negative_delta = 10; // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
repeated double negative_count = 11; // Absolute count of each bucket.
// Positive buckets for the native histogram.
repeated BucketSpan positive_span = 12;
// Use either "positive_delta" or "positive_count", the former for
// regular histograms with integer counts, the latter for float
// histograms.
repeated sint64 positive_delta = 13; // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
repeated double positive_count = 14; // Absolute count of each bucket.
}
message Bucket {
uint64 cumulative_count = 1; // Cumulative in increasing order.
double cumulative_count_float = 4; // Overrides cumulative_count if > 0.
double upper_bound = 2; // Inclusive.
Exemplar exemplar = 3;
}
// A BucketSpan defines a number of consecutive buckets in a native
// histogram with their offset. Logically, it would be more
// straightforward to include the bucket counts in the Span. However,
// the protobuf representation is more compact in the way the data is
// structured here (with all the buckets in a single array separate
// from the Spans).
message BucketSpan {
sint32 offset = 1; // Gap to previous span, or starting point for 1st span (which can be negative).
uint32 length = 2; // Length of consecutive buckets.
}
message Exemplar {
repeated LabelPair label = 1;
double value = 2;
google.protobuf.Timestamp timestamp = 3; // OpenMetrics-style.
}
message Metric {
repeated LabelPair label = 1;
Gauge gauge = 2;
Counter counter = 3;
Summary summary = 4;
Untyped untyped = 5;
Histogram histogram = 7;
int64 timestamp_ms = 6;
}
message MetricFamily {
string name = 1;
string help = 2;
MetricType type = 3;
repeated Metric metric = 4;
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,34 +14,41 @@
package promql
import (
"fmt"
"math"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/grafana/regexp"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser"
)
// FunctionCall is the type of a PromQL function implementation
//
// vals is a list of the evaluated arguments for the function call.
// For range vectors it will be a Matrix with one series, instant vectors a
// Vector, scalars a Vector with one series whose value is the scalar
// value,and nil for strings.
//
// For range vectors it will be a Matrix with one series, instant vectors a
// Vector, scalars a Vector with one series whose value is the scalar
// value,and nil for strings.
//
// args are the original arguments to the function, where you can access
// matrixSelectors, vectorSelectors, and StringLiterals.
// matrixSelectors, vectorSelectors, and StringLiterals.
//
// enh.Out is a pre-allocated empty vector that you may use to accumulate
// output before returning it. The vectors in vals should not be returned.a
// output before returning it. The vectors in vals should not be returned.a
//
// Range vector functions need only return a vector with the right value,
// the metric and timestamp are not needed.
// the metric and timestamp are not needed.
//
// Instant vector functions need only return a vector with the right values and
// metrics, the timestamp are not needed.
// metrics, the timestamp are not needed.
//
// Scalar results should be returned as the value of a sample in a Vector.
type FunctionCall func(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector
@@ -56,14 +63,15 @@ func funcTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper)
// It calculates the rate (allowing for counter resets if isCounter is true),
// extrapolates if the first/last sample is close to the boundary, and returns
// the result as either per-second (if isRate is true) or overall.
func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper, isCounter bool, isRate bool) Vector {
func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper, isCounter, isRate bool) Vector {
ms := args[0].(*parser.MatrixSelector)
vs := ms.VectorSelector.(*parser.VectorSelector)
var (
samples = vals[0].(Matrix)[0]
rangeStart = enh.Ts - durationMilliseconds(ms.Range+vs.Offset)
rangeEnd = enh.Ts - durationMilliseconds(vs.Offset)
samples = vals[0].(Matrix)[0]
rangeStart = enh.Ts - durationMilliseconds(ms.Range+vs.Offset)
rangeEnd = enh.Ts - durationMilliseconds(vs.Offset)
resultValue float64
resultHistogram *histogram.FloatHistogram
)
// No sense in trying to compute a rate without at least two points. Drop
@@ -71,17 +79,35 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
if len(samples.Points) < 2 {
return enh.Out
}
var (
counterCorrection float64
lastValue float64
)
for _, sample := range samples.Points {
if isCounter && sample.V < lastValue {
counterCorrection += lastValue
if samples.Points[0].H != nil {
resultHistogram = histogramRate(samples.Points, isCounter)
if resultHistogram == nil {
// Points are a mix of floats and histograms, or the histograms
// are not compatible with each other.
// TODO(beorn7): find a way of communicating the exact reason
return enh.Out
}
} else {
resultValue = samples.Points[len(samples.Points)-1].V - samples.Points[0].V
prevValue := samples.Points[0].V
// We have to iterate through everything even in the non-counter
// case because we have to check that everything is a float.
// TODO(beorn7): Find a way to check that earlier, e.g. by
// handing in a []FloatPoint and a []HistogramPoint separately.
for _, currPoint := range samples.Points[1:] {
if currPoint.H != nil {
return nil // Range contains a mix of histograms and floats.
}
if !isCounter {
continue
}
if currPoint.V < prevValue {
resultValue += prevValue
}
prevValue = currPoint.V
}
lastValue = sample.V
}
resultValue := lastValue - samples.Points[0].V + counterCorrection
// Duration between first/last samples and boundary of range.
durationToStart := float64(samples.Points[0].T-rangeStart) / 1000
@@ -90,6 +116,7 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
sampledInterval := float64(samples.Points[len(samples.Points)-1].T-samples.Points[0].T) / 1000
averageDurationBetweenSamples := sampledInterval / float64(len(samples.Points)-1)
// TODO(beorn7): Do this for histograms, too.
if isCounter && resultValue > 0 && samples.Points[0].V >= 0 {
// Counters cannot be negative. If we have any slope at
// all (i.e. resultValue went up), we can extrapolate
@@ -121,16 +148,69 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
} else {
extrapolateToInterval += averageDurationBetweenSamples / 2
}
resultValue = resultValue * (extrapolateToInterval / sampledInterval)
factor := extrapolateToInterval / sampledInterval
if isRate {
resultValue = resultValue / ms.Range.Seconds()
factor /= ms.Range.Seconds()
}
if resultHistogram == nil {
resultValue *= factor
} else {
resultHistogram.Scale(factor)
}
return append(enh.Out, Sample{
Point: Point{V: resultValue},
Point: Point{V: resultValue, H: resultHistogram},
})
}
// histogramRate is a helper function for extrapolatedRate. It requires
// points[0] to be a histogram. It returns nil if any other Point in points is
// not a histogram.
func histogramRate(points []Point, isCounter bool) *histogram.FloatHistogram {
prev := points[0].H // We already know that this is a histogram.
last := points[len(points)-1].H
if last == nil {
return nil // Range contains a mix of histograms and floats.
}
minSchema := prev.Schema
if last.Schema < minSchema {
minSchema = last.Schema
}
// First iteration to find out two things:
// - What's the smallest relevant schema?
// - Are all data points histograms?
// TODO(beorn7): Find a way to check that earlier, e.g. by handing in a
// []FloatPoint and a []HistogramPoint separately.
for _, currPoint := range points[1 : len(points)-1] {
curr := currPoint.H
if curr == nil {
return nil // Range contains a mix of histograms and floats.
}
if !isCounter {
continue
}
if curr.Schema < minSchema {
minSchema = curr.Schema
}
}
h := last.CopyToSchema(minSchema)
h.Sub(prev)
if isCounter {
// Second iteration to deal with counter resets.
for _, currPoint := range points[1:] {
curr := currPoint.H
if curr.DetectReset(prev) {
h.Add(prev)
}
prev = curr
}
}
return h.Compact(0)
}
// === delta(Matrix parser.ValueTypeMatrix) Vector ===
func funcDelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return extrapolatedRate(vals, args, enh, false, false)
@@ -222,12 +302,12 @@ func funcHoltWinters(vals []parser.Value, args parser.Expressions, enh *EvalNode
// The trend factor argument.
tf := vals[2].(Vector)[0].V
// Sanity check the input.
// Check that the input parameters are valid.
if sf <= 0 || sf >= 1 {
panic(errors.Errorf("invalid smoothing factor. Expected: 0 < sf < 1, got: %f", sf))
panic(fmt.Errorf("invalid smoothing factor. Expected: 0 < sf < 1, got: %f", sf))
}
if tf <= 0 || tf >= 1 {
panic(errors.Errorf("invalid trend factor. Expected: 0 < tf < 1, got: %f", tf))
panic(fmt.Errorf("invalid trend factor. Expected: 0 < tf < 1, got: %f", tf))
}
l := len(samples.Points)
@@ -279,6 +359,23 @@ func funcSortDesc(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel
return Vector(byValueSorter)
}
// === clamp(Vector parser.ValueTypeVector, min, max Scalar) Vector ===
func funcClamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
vec := vals[0].(Vector)
min := vals[1].(Vector)[0].Point.V
max := vals[2].(Vector)[0].Point.V
if max < min {
return enh.Out
}
for _, el := range vec {
enh.Out = append(enh.Out, Sample{
Metric: enh.DropMetricName(el.Metric),
Point: Point{V: math.Max(min, math.Min(max, el.V))},
})
}
return enh.Out
}
// === clamp_max(Vector parser.ValueTypeVector, max Scalar) Vector ===
func funcClampMax(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
vec := vals[0].(Vector)
@@ -351,7 +448,7 @@ func aggrOverTime(vals []parser.Value, enh *EvalNodeHelper, aggrFn func([]Point)
// === avg_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcAvgOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
var mean, count float64
var mean, count, c float64
for _, v := range values {
count++
if math.IsInf(mean, 0) {
@@ -371,9 +468,13 @@ func funcAvgOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNode
continue
}
}
mean += v.V/count - mean/count
mean, c = kahanSumInc(v.V/count-mean/count, mean, c)
}
return mean
if math.IsInf(mean, 0) {
return mean
}
return mean + c
})
}
@@ -384,7 +485,16 @@ func funcCountOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNo
})
}
// === floor(Vector parser.ValueTypeVector) Vector ===
// === last_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcLastOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
el := vals[0].(Matrix)[0]
return append(enh.Out, Sample{
Metric: el.Metric,
Point: Point{V: el.Points[len(el.Points)-1].V},
})
}
// === max_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcMaxOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
@@ -414,11 +524,14 @@ func funcMinOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNode
// === sum_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcSumOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
var sum float64
var sum, c float64
for _, v := range values {
sum += v.V
sum, c = kahanSumInc(v.V, sum, c)
}
return sum
if math.IsInf(sum, 0) {
return sum
}
return sum + c
})
}
@@ -439,28 +552,32 @@ func funcQuantileOverTime(vals []parser.Value, args parser.Expressions, enh *Eva
// === stddev_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcStddevOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
var aux, count, mean float64
var count float64
var mean, cMean float64
var aux, cAux float64
for _, v := range values {
count++
delta := v.V - mean
mean += delta / count
aux += delta * (v.V - mean)
delta := v.V - (mean + cMean)
mean, cMean = kahanSumInc(delta/count, mean, cMean)
aux, cAux = kahanSumInc(delta*(v.V-(mean+cMean)), aux, cAux)
}
return math.Sqrt(aux / count)
return math.Sqrt((aux + cAux) / count)
})
}
// === stdvar_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcStdvarOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
var aux, count, mean float64
var count float64
var mean, cMean float64
var aux, cAux float64
for _, v := range values {
count++
delta := v.V - mean
mean += delta / count
aux += delta * (v.V - mean)
delta := v.V - (mean + cMean)
mean, cMean = kahanSumInc(delta/count, mean, cMean)
aux, cAux = kahanSumInc(delta*(v.V-(mean+cMean)), aux, cAux)
}
return aux / count
return (aux + cAux) / count
})
}
@@ -488,6 +605,13 @@ func funcAbsentOverTime(vals []parser.Value, args parser.Expressions, enh *EvalN
})
}
// === present_over_time(Vector parser.ValueTypeMatrix) Vector ===
func funcPresentOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
return 1
})
}
func simpleFunc(vals []parser.Value, enh *EvalNodeHelper, f func(float64) float64) Vector {
for _, el := range vals[0].(Vector) {
enh.Out = append(enh.Out, Sample{
@@ -538,6 +662,99 @@ func funcLog10(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper
return simpleFunc(vals, enh, math.Log10)
}
// === sin(Vector parser.ValueTypeVector) Vector ===
func funcSin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Sin)
}
// === cos(Vector parser.ValueTypeVector) Vector ===
func funcCos(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Cos)
}
// === tan(Vector parser.ValueTypeVector) Vector ===
func funcTan(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Tan)
}
// == asin(Vector parser.ValueTypeVector) Vector ===
func funcAsin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Asin)
}
// == acos(Vector parser.ValueTypeVector) Vector ===
func funcAcos(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Acos)
}
// == atan(Vector parser.ValueTypeVector) Vector ===
func funcAtan(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Atan)
}
// == sinh(Vector parser.ValueTypeVector) Vector ===
func funcSinh(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Sinh)
}
// == cosh(Vector parser.ValueTypeVector) Vector ===
func funcCosh(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Cosh)
}
// == tanh(Vector parser.ValueTypeVector) Vector ===
func funcTanh(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Tanh)
}
// == asinh(Vector parser.ValueTypeVector) Vector ===
func funcAsinh(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Asinh)
}
// == acosh(Vector parser.ValueTypeVector) Vector ===
func funcAcosh(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Acosh)
}
// == atanh(Vector parser.ValueTypeVector) Vector ===
func funcAtanh(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, math.Atanh)
}
// === rad(Vector parser.ValueTypeVector) Vector ===
func funcRad(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, func(v float64) float64 {
return v * math.Pi / 180
})
}
// === deg(Vector parser.ValueTypeVector) Vector ===
func funcDeg(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, func(v float64) float64 {
return v * 180 / math.Pi
})
}
// === pi() Scalar ===
func funcPi(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return Vector{Sample{Point: Point{
V: math.Pi,
}}}
}
// === sgn(Vector parser.ValueTypeVector) Vector ===
func funcSgn(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, func(v float64) float64 {
if v < 0 {
return -1
} else if v > 0 {
return 1
}
return v
})
}
// === timestamp(Vector parser.ValueTypeVector) Vector ===
func funcTimestamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
vec := vals[0].(Vector)
@@ -550,23 +767,64 @@ func funcTimestamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe
return enh.Out
}
func kahanSum(samples []float64) float64 {
var sum, c float64
for _, v := range samples {
sum, c = kahanSumInc(v, sum, c)
}
return sum + c
}
func kahanSumInc(inc, sum, c float64) (newSum, newC float64) {
t := sum + inc
// Using Neumaier improvement, swap if next term larger than sum.
if math.Abs(sum) >= math.Abs(inc) {
c += (sum - t) + inc
} else {
c += (inc - t) + sum
}
return t, c
}
// linearRegression performs a least-square linear regression analysis on the
// provided SamplePairs. It returns the slope, and the intercept value at the
// provided time.
func linearRegression(samples []Point, interceptTime int64) (slope, intercept float64) {
var (
n float64
sumX, sumY float64
sumXY, sumX2 float64
n float64
sumX, cX float64
sumY, cY float64
sumXY, cXY float64
sumX2, cX2 float64
initY float64
constY bool
)
for _, sample := range samples {
x := float64(sample.T-interceptTime) / 1e3
initY = samples[0].V
constY = true
for i, sample := range samples {
// Set constY to false if any new y values are encountered.
if constY && i > 0 && sample.V != initY {
constY = false
}
n += 1.0
sumY += sample.V
sumX += x
sumXY += x * sample.V
sumX2 += x * x
x := float64(sample.T-interceptTime) / 1e3
sumX, cX = kahanSumInc(x, sumX, cX)
sumY, cY = kahanSumInc(sample.V, sumY, cY)
sumXY, cXY = kahanSumInc(x*sample.V, sumXY, cXY)
sumX2, cX2 = kahanSumInc(x*x, sumX2, cX2)
}
if constY {
if math.IsInf(initY, 0) {
return math.NaN(), math.NaN()
}
return 0, initY
}
sumX = sumX + cX
sumY = sumY + cY
sumXY = sumXY + cXY
sumX2 = sumX2 + cX2
covXY := sumXY - sumX*sumY/n
varX := sumX2 - sumX*sumX/n
@@ -598,7 +856,6 @@ func funcDeriv(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper
func funcPredictLinear(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
samples := vals[0].(Matrix)[0]
duration := vals[1].(Vector)[0].V
// No sense in trying to predict anything without at least two points.
// Drop this Vector element.
if len(samples.Points) < 2 {
@@ -611,11 +868,63 @@ func funcPredictLinear(vals []parser.Value, args parser.Expressions, enh *EvalNo
})
}
// === histogram_count(Vector parser.ValueTypeVector) Vector ===
func funcHistogramCount(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
inVec := vals[0].(Vector)
for _, sample := range inVec {
// Skip non-histogram samples.
if sample.H == nil {
continue
}
enh.Out = append(enh.Out, Sample{
Metric: enh.DropMetricName(sample.Metric),
Point: Point{V: sample.H.Count},
})
}
return enh.Out
}
// === histogram_sum(Vector parser.ValueTypeVector) Vector ===
func funcHistogramSum(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
inVec := vals[0].(Vector)
for _, sample := range inVec {
// Skip non-histogram samples.
if sample.H == nil {
continue
}
enh.Out = append(enh.Out, Sample{
Metric: enh.DropMetricName(sample.Metric),
Point: Point{V: sample.H.Sum},
})
}
return enh.Out
}
// === histogram_fraction(lower, upper parser.ValueTypeScalar, Vector parser.ValueTypeVector) Vector ===
func funcHistogramFraction(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
lower := vals[0].(Vector)[0].V
upper := vals[1].(Vector)[0].V
inVec := vals[2].(Vector)
for _, sample := range inVec {
// Skip non-histogram samples.
if sample.H == nil {
continue
}
enh.Out = append(enh.Out, Sample{
Metric: enh.DropMetricName(sample.Metric),
Point: Point{V: histogramFraction(lower, upper, sample.H)},
})
}
return enh.Out
}
// === histogram_quantile(k parser.ValueTypeScalar, Vector parser.ValueTypeVector) Vector ===
func funcHistogramQuantile(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
q := vals[0].(Vector)[0].V
inVec := vals[1].(Vector)
sigf := signatureFunc(false, enh.lblBuf, excludedLabels...)
if enh.signatureToMetricWithBuckets == nil {
enh.signatureToMetricWithBuckets = map[string]*metricWithBuckets{}
@@ -624,27 +933,57 @@ func funcHistogramQuantile(vals []parser.Value, args parser.Expressions, enh *Ev
v.buckets = v.buckets[:0]
}
}
for _, el := range inVec {
var histogramSamples []Sample
for _, sample := range inVec {
// We are only looking for conventional buckets here. Remember
// the histograms for later treatment.
if sample.H != nil {
histogramSamples = append(histogramSamples, sample)
continue
}
upperBound, err := strconv.ParseFloat(
el.Metric.Get(model.BucketLabel), 64,
sample.Metric.Get(model.BucketLabel), 64,
)
if err != nil {
// Oops, no bucket label or malformed label value. Skip.
// TODO(beorn7): Issue a warning somehow.
continue
}
l := sigf(el.Metric)
mb, ok := enh.signatureToMetricWithBuckets[l]
enh.lblBuf = sample.Metric.BytesWithoutLabels(enh.lblBuf, labels.BucketLabel)
mb, ok := enh.signatureToMetricWithBuckets[string(enh.lblBuf)]
if !ok {
el.Metric = labels.NewBuilder(el.Metric).
Del(labels.BucketLabel, labels.MetricName).
Labels()
sample.Metric = labels.NewBuilder(sample.Metric).
Del(excludedLabels...).
Labels(labels.EmptyLabels())
mb = &metricWithBuckets{el.Metric, nil}
enh.signatureToMetricWithBuckets[l] = mb
mb = &metricWithBuckets{sample.Metric, nil}
enh.signatureToMetricWithBuckets[string(enh.lblBuf)] = mb
}
mb.buckets = append(mb.buckets, bucket{upperBound, el.V})
mb.buckets = append(mb.buckets, bucket{upperBound, sample.V})
}
// Now deal with the histograms.
for _, sample := range histogramSamples {
// We have to reconstruct the exact same signature as above for
// a conventional histogram, just ignoring any le label.
enh.lblBuf = sample.Metric.Bytes(enh.lblBuf)
if mb, ok := enh.signatureToMetricWithBuckets[string(enh.lblBuf)]; ok && len(mb.buckets) > 0 {
// At this data point, we have conventional histogram
// buckets and a native histogram with the same name and
// labels. Do not evaluate anything.
// TODO(beorn7): Issue a warning somehow.
delete(enh.signatureToMetricWithBuckets, string(enh.lblBuf))
continue
}
enh.Out = append(enh.Out, Sample{
Metric: enh.DropMetricName(sample.Metric),
Point: Point{V: histogramQuantile(q, sample.H)},
})
}
for _, mb := range enh.signatureToMetricWithBuckets {
@@ -701,20 +1040,20 @@ func funcChanges(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelp
func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
var (
vector = vals[0].(Vector)
dst = args[1].(*parser.StringLiteral).Val
repl = args[2].(*parser.StringLiteral).Val
src = args[3].(*parser.StringLiteral).Val
regexStr = args[4].(*parser.StringLiteral).Val
dst = stringFromArg(args[1])
repl = stringFromArg(args[2])
src = stringFromArg(args[3])
regexStr = stringFromArg(args[4])
)
if enh.regex == nil {
var err error
enh.regex, err = regexp.Compile("^(?:" + regexStr + ")$")
if err != nil {
panic(errors.Errorf("invalid regular expression in label_replace(): %s", regexStr))
panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr))
}
if !model.LabelNameRE.MatchString(dst) {
panic(errors.Errorf("invalid destination label name in label_replace(): %s", dst))
panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst))
}
enh.Dmn = make(map[uint64]labels.Labels, len(enh.Out))
}
@@ -738,7 +1077,7 @@ func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNod
if len(res) > 0 {
lb.Set(dst, string(res))
}
outMetric = lb.Labels()
outMetric = lb.Labels(labels.EmptyLabels())
enh.Dmn[h] = outMetric
}
}
@@ -764,8 +1103,8 @@ func funcVector(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelpe
func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
var (
vector = vals[0].(Vector)
dst = args[1].(*parser.StringLiteral).Val
sep = args[2].(*parser.StringLiteral).Val
dst = stringFromArg(args[1])
sep = stringFromArg(args[2])
srcLabels = make([]string, len(args)-3)
)
@@ -774,15 +1113,15 @@ func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe
}
for i := 3; i < len(args); i++ {
src := args[i].(*parser.StringLiteral).Val
src := stringFromArg(args[i])
if !model.LabelName(src).IsValid() {
panic(errors.Errorf("invalid source label name in label_join(): %s", src))
panic(fmt.Errorf("invalid source label name in label_join(): %s", src))
}
srcLabels[i-3] = src
}
if !model.LabelName(dst).IsValid() {
panic(errors.Errorf("invalid destination label name in label_join(): %s", dst))
panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst))
}
srcVals := make([]string, len(srcLabels))
@@ -806,7 +1145,7 @@ func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe
lb.Set(dst, strval)
}
outMetric = lb.Labels()
outMetric = lb.Labels(labels.EmptyLabels())
enh.Dmn[h] = outMetric
}
@@ -859,6 +1198,13 @@ func funcDayOfWeek(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe
})
}
// === day_of_year(v Vector) Scalar ===
func funcDayOfYear(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return dateWrapper(vals, enh, func(t time.Time) float64 {
return float64(t.YearDay())
})
}
// === hour(v Vector) Scalar ===
func funcHour(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return dateWrapper(vals, enh, func(t time.Time) float64 {
@@ -892,20 +1238,34 @@ var FunctionCalls = map[string]FunctionCall{
"abs": funcAbs,
"absent": funcAbsent,
"absent_over_time": funcAbsentOverTime,
"acos": funcAcos,
"acosh": funcAcosh,
"asin": funcAsin,
"asinh": funcAsinh,
"atan": funcAtan,
"atanh": funcAtanh,
"avg_over_time": funcAvgOverTime,
"ceil": funcCeil,
"changes": funcChanges,
"clamp": funcClamp,
"clamp_max": funcClampMax,
"clamp_min": funcClampMin,
"cos": funcCos,
"cosh": funcCosh,
"count_over_time": funcCountOverTime,
"days_in_month": funcDaysInMonth,
"day_of_month": funcDayOfMonth,
"day_of_week": funcDayOfWeek,
"day_of_year": funcDayOfYear,
"deg": funcDeg,
"delta": funcDelta,
"deriv": funcDeriv,
"exp": funcExp,
"floor": funcFloor,
"histogram_count": funcHistogramCount,
"histogram_fraction": funcHistogramFraction,
"histogram_quantile": funcHistogramQuantile,
"histogram_sum": funcHistogramSum,
"holt_winters": funcHoltWinters,
"hour": funcHour,
"idelta": funcIdelta,
@@ -916,28 +1276,52 @@ var FunctionCalls = map[string]FunctionCall{
"ln": funcLn,
"log10": funcLog10,
"log2": funcLog2,
"last_over_time": funcLastOverTime,
"max_over_time": funcMaxOverTime,
"min_over_time": funcMinOverTime,
"minute": funcMinute,
"month": funcMonth,
"pi": funcPi,
"predict_linear": funcPredictLinear,
"present_over_time": funcPresentOverTime,
"quantile_over_time": funcQuantileOverTime,
"rad": funcRad,
"rate": funcRate,
"resets": funcResets,
"round": funcRound,
"scalar": funcScalar,
"sgn": funcSgn,
"sin": funcSin,
"sinh": funcSinh,
"sort": funcSort,
"sort_desc": funcSortDesc,
"sqrt": funcSqrt,
"stddev_over_time": funcStddevOverTime,
"stdvar_over_time": funcStdvarOverTime,
"sum_over_time": funcSumOverTime,
"tan": funcTan,
"tanh": funcTanh,
"time": funcTime,
"timestamp": funcTimestamp,
"vector": funcVector,
"year": funcYear,
}
// AtModifierUnsafeFunctions are the functions whose result
// can vary if evaluation time is changed when the arguments are
// step invariant. It also includes functions that use the timestamps
// of the passed instant vector argument to calculate a result since
// that can also change with change in eval time.
var AtModifierUnsafeFunctions = map[string]struct{}{
// Step invariant functions.
"days_in_month": {}, "day_of_month": {}, "day_of_week": {}, "day_of_year": {},
"hour": {}, "minute": {}, "month": {}, "year": {},
"predict_linear": {}, "time": {},
// Uses timestamp of the argument for the result,
// hence unsafe to use with @ modifier.
"timestamp": {},
}
type vectorByValueHeap Vector
func (s vectorByValueHeap) Len() int {
@@ -999,7 +1383,7 @@ func (s *vectorByReverseValueHeap) Pop() interface{} {
// createLabelsForAbsentFunction returns the labels that are uniquely and exactly matched
// in a given expression. It is used in the absent functions.
func createLabelsForAbsentFunction(expr parser.Expr) labels.Labels {
m := labels.Labels{}
b := labels.NewBuilder(labels.EmptyLabels())
var lm []*labels.Matcher
switch n := expr.(type) {
@@ -1008,23 +1392,30 @@ func createLabelsForAbsentFunction(expr parser.Expr) labels.Labels {
case *parser.MatrixSelector:
lm = n.VectorSelector.(*parser.VectorSelector).LabelMatchers
default:
return m
return labels.EmptyLabels()
}
empty := []string{}
// The 'has' map implements backwards-compatibility for historic behaviour:
// e.g. in `absent(x{job="a",job="b",foo="bar"})` then `job` is removed from the output.
// Note this gives arguably wrong behaviour for `absent(x{job="a",job="a",foo="bar"})`.
has := make(map[string]bool, len(lm))
for _, ma := range lm {
if ma.Name == labels.MetricName {
continue
}
if ma.Type == labels.MatchEqual && !m.Has(ma.Name) {
m = labels.NewBuilder(m).Set(ma.Name, ma.Value).Labels()
if ma.Type == labels.MatchEqual && !has[ma.Name] {
b.Set(ma.Name, ma.Value)
has[ma.Name] = true
} else {
empty = append(empty, ma.Name)
b.Del(ma.Name)
}
}
for _, v := range empty {
m = labels.NewBuilder(m).Del(v).Labels()
}
return m
return b.Labels(labels.EmptyLabels())
}
func stringFromArg(e parser.Expr) string {
tmp := unwrapStepInvariantExpr(e) // Unwrap StepInvariant
unwrapParenExpr(&tmp) // Optionally unwrap ParenExpr
return tmp.(*parser.StringLiteral).Val
}

View File

@@ -12,14 +12,16 @@
// limitations under the License.
// Only build when go-fuzz is in use
//go:build gofuzz
// +build gofuzz
package promql
import (
"errors"
"io"
"github.com/prometheus/prometheus/pkg/textparse"
"github.com/prometheus/prometheus/model/textparse"
"github.com/prometheus/prometheus/promql/parser"
)
@@ -56,7 +58,13 @@ const (
)
func fuzzParseMetricWithContentType(in []byte, contentType string) int {
p := textparse.New(in, contentType)
p, warning := textparse.New(in, contentType)
if warning != nil {
// An invalid content type is being passed, which should not happen
// in this context.
panic(warning)
}
var err error
for {
_, err = p.Next()
@@ -64,7 +72,7 @@ func fuzzParseMetricWithContentType(in []byte, contentType string) int {
break
}
}
if err == io.EOF {
if errors.Is(err, io.EOF) {
err = nil
}

View File

@@ -15,11 +15,10 @@ package parser
import (
"context"
"fmt"
"time"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/storage"
)
@@ -29,18 +28,22 @@ import (
// or a chain of function definitions (e.g. String(), PromQLExpr(), etc.) convention is
// to list them as follows:
//
// - Statements
// - statement types (alphabetical)
// - ...
// - Expressions
// - expression types (alphabetical)
// - ...
//
// - Statements
// - statement types (alphabetical)
// - ...
// - Expressions
// - expression types (alphabetical)
// - ...
type Node interface {
// String representation of the node that returns the given node when parsed
// as part of a valid query.
String() string
// Pretty returns the prettified representation of the node.
// It uses the level information to determine at which level/depth the current
// node is in the AST and uses this to apply indentation.
Pretty(level int) string
// PositionRange returns the position of the AST Node in the query string.
PositionRange() PositionRange
}
@@ -64,6 +67,8 @@ type EvalStmt struct {
Start, End time.Time
// Time between two evaluated instants for the range [Start:End].
Interval time.Duration
// Lookback delta to use for this evaluation.
LookbackDelta time.Duration
}
func (*EvalStmt) PromQLStmt() {}
@@ -125,10 +130,18 @@ type MatrixSelector struct {
// SubqueryExpr represents a subquery.
type SubqueryExpr struct {
Expr Expr
Range time.Duration
Offset time.Duration
Step time.Duration
Expr Expr
Range time.Duration
// OriginalOffset is the actual offset that was set in the query.
// This never changes.
OriginalOffset time.Duration
// Offset is the offset used during the query execution
// which is calculated using the original offset, at modifier time,
// eval time, and subquery offsets in the AST tree.
Offset time.Duration
Timestamp *int64
StartOrEnd ItemType // Set when @ is used with start() or end()
Step time.Duration
EndPos Pos
}
@@ -162,10 +175,29 @@ type UnaryExpr struct {
StartPos Pos
}
// StepInvariantExpr represents a query which evaluates to the same result
// irrespective of the evaluation time given the raw samples from TSDB remain unchanged.
// Currently this is only used for engine optimisations and the parser does not produce this.
type StepInvariantExpr struct {
Expr Expr
}
func (e *StepInvariantExpr) String() string { return e.Expr.String() }
func (e *StepInvariantExpr) PositionRange() PositionRange { return e.Expr.PositionRange() }
// VectorSelector represents a Vector selection.
type VectorSelector struct {
Name string
Name string
// OriginalOffset is the actual offset that was set in the query.
// This never changes.
OriginalOffset time.Duration
// Offset is the offset used during the query execution
// which is calculated using the original offset, at modifier time,
// eval time, and subquery offsets in the AST tree.
Offset time.Duration
Timestamp *int64
StartOrEnd ItemType // Set when @ is used with start() or end()
LabelMatchers []*labels.Matcher
// The unexpanded seriesSet populated at query preparation time.
@@ -179,8 +211,9 @@ type VectorSelector struct {
// of an arbitrary function during handling. It is used to test the Engine.
type TestStmt func(context.Context) error
func (TestStmt) String() string { return "test statement" }
func (TestStmt) PromQLStmt() {}
func (TestStmt) String() string { return "test statement" }
func (TestStmt) PromQLStmt() {}
func (t TestStmt) Pretty(int) string { return t.String() }
func (TestStmt) PositionRange() PositionRange {
return PositionRange{
@@ -203,17 +236,19 @@ func (e *BinaryExpr) Type() ValueType {
}
return ValueTypeVector
}
func (e *StepInvariantExpr) Type() ValueType { return e.Expr.Type() }
func (*AggregateExpr) PromQLExpr() {}
func (*BinaryExpr) PromQLExpr() {}
func (*Call) PromQLExpr() {}
func (*MatrixSelector) PromQLExpr() {}
func (*SubqueryExpr) PromQLExpr() {}
func (*NumberLiteral) PromQLExpr() {}
func (*ParenExpr) PromQLExpr() {}
func (*StringLiteral) PromQLExpr() {}
func (*UnaryExpr) PromQLExpr() {}
func (*VectorSelector) PromQLExpr() {}
func (*AggregateExpr) PromQLExpr() {}
func (*BinaryExpr) PromQLExpr() {}
func (*Call) PromQLExpr() {}
func (*MatrixSelector) PromQLExpr() {}
func (*SubqueryExpr) PromQLExpr() {}
func (*NumberLiteral) PromQLExpr() {}
func (*ParenExpr) PromQLExpr() {}
func (*StringLiteral) PromQLExpr() {}
func (*UnaryExpr) PromQLExpr() {}
func (*VectorSelector) PromQLExpr() {}
func (*StepInvariantExpr) PromQLExpr() {}
// VectorMatchCardinality describes the cardinality relationship
// of two Vectors in a binary operation.
@@ -287,6 +322,18 @@ func Walk(v Visitor, node Node, path []Node) error {
return err
}
func ExtractSelectors(expr Expr) [][]*labels.Matcher {
var selectors [][]*labels.Matcher
Inspect(expr, func(node Node, _ []Node) error {
vs, ok := node.(*VectorSelector)
if ok {
selectors = append(selectors, vs.LabelMatchers)
}
return nil
})
return selectors
}
type inspector func(Node, []Node) error
func (f inspector) Visit(node Node, path []Node) (Visitor, error) {
@@ -347,11 +394,13 @@ func Children(node Node) []Node {
return []Node{n.Expr}
case *MatrixSelector:
return []Node{n.VectorSelector}
case *StepInvariantExpr:
return []Node{n.Expr}
case *NumberLiteral, *StringLiteral, *VectorSelector:
// nothing to do
return []Node{}
default:
panic(errors.Errorf("promql.Children: unhandled node type %T", node))
panic(fmt.Errorf("promql.Children: unhandled node type %T", node))
}
}
@@ -364,7 +413,7 @@ type PositionRange struct {
// mergeRanges is a helper function to merge the PositionRanges of two Nodes.
// Note that the arguments must be in the same order as they
// occur in the input string.
func mergeRanges(first Node, last Node) PositionRange {
func mergeRanges(first, last Node) PositionRange {
return PositionRange{
Start: first.PositionRange().Start,
End: last.PositionRange().End,
@@ -383,15 +432,19 @@ func (i *Item) PositionRange() PositionRange {
func (e *AggregateExpr) PositionRange() PositionRange {
return e.PosRange
}
func (e *BinaryExpr) PositionRange() PositionRange {
return mergeRanges(e.LHS, e.RHS)
}
func (e *Call) PositionRange() PositionRange {
return e.PosRange
}
func (e *EvalStmt) PositionRange() PositionRange {
return e.Expr.PositionRange()
}
func (e Expressions) PositionRange() PositionRange {
if len(e) == 0 {
// Position undefined.
@@ -402,33 +455,40 @@ func (e Expressions) PositionRange() PositionRange {
}
return mergeRanges(e[0], e[len(e)-1])
}
func (e *MatrixSelector) PositionRange() PositionRange {
return PositionRange{
Start: e.VectorSelector.PositionRange().Start,
End: e.EndPos,
}
}
func (e *SubqueryExpr) PositionRange() PositionRange {
return PositionRange{
Start: e.Expr.PositionRange().Start,
End: e.EndPos,
}
}
func (e *NumberLiteral) PositionRange() PositionRange {
return e.PosRange
}
func (e *ParenExpr) PositionRange() PositionRange {
return e.PosRange
}
func (e *StringLiteral) PositionRange() PositionRange {
return e.PosRange
}
func (e *UnaryExpr) PositionRange() PositionRange {
return PositionRange{
Start: e.StartPos,
End: e.Expr.PositionRange().End,
}
}
func (e *VectorSelector) PositionRange() PositionRange {
return e.PosRange
}

View File

@@ -39,6 +39,36 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"acos": {
Name: "acos",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"acosh": {
Name: "acosh",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"asin": {
Name: "asin",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"asinh": {
Name: "asinh",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"atan": {
Name: "atan",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"atanh": {
Name: "atanh",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"avg_over_time": {
Name: "avg_over_time",
ArgTypes: []ValueType{ValueTypeMatrix},
@@ -54,6 +84,11 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"clamp": {
Name: "clamp",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar, ValueTypeScalar},
ReturnType: ValueTypeVector,
},
"clamp_max": {
Name: "clamp_max",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
@@ -64,6 +99,16 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
ReturnType: ValueTypeVector,
},
"cos": {
Name: "cos",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"cosh": {
Name: "cosh",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"count_over_time": {
Name: "count_over_time",
ArgTypes: []ValueType{ValueTypeMatrix},
@@ -87,6 +132,17 @@ var Functions = map[string]*Function{
Variadic: 1,
ReturnType: ValueTypeVector,
},
"day_of_year": {
Name: "day_of_year",
ArgTypes: []ValueType{ValueTypeVector},
Variadic: 1,
ReturnType: ValueTypeVector,
},
"deg": {
Name: "deg",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"delta": {
Name: "delta",
ArgTypes: []ValueType{ValueTypeMatrix},
@@ -107,6 +163,21 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"histogram_count": {
Name: "histogram_count",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"histogram_sum": {
Name: "histogram_sum",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"histogram_fraction": {
Name: "histogram_fraction",
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeScalar, ValueTypeVector},
ReturnType: ValueTypeVector,
},
"histogram_quantile": {
Name: "histogram_quantile",
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeVector},
@@ -149,6 +220,11 @@ var Functions = map[string]*Function{
Variadic: -1,
ReturnType: ValueTypeVector,
},
"last_over_time": {
Name: "last_over_time",
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"ln": {
Name: "ln",
ArgTypes: []ValueType{ValueTypeVector},
@@ -186,16 +262,31 @@ var Functions = map[string]*Function{
Variadic: 1,
ReturnType: ValueTypeVector,
},
"pi": {
Name: "pi",
ArgTypes: []ValueType{},
ReturnType: ValueTypeScalar,
},
"predict_linear": {
Name: "predict_linear",
ArgTypes: []ValueType{ValueTypeMatrix, ValueTypeScalar},
ReturnType: ValueTypeVector,
},
"present_over_time": {
Name: "present_over_time",
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"quantile_over_time": {
Name: "quantile_over_time",
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"rad": {
Name: "rad",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"rate": {
Name: "rate",
ArgTypes: []ValueType{ValueTypeMatrix},
@@ -217,6 +308,21 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeScalar,
},
"sgn": {
Name: "sgn",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"sin": {
Name: "sin",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"sinh": {
Name: "sinh",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"sort": {
Name: "sort",
ArgTypes: []ValueType{ValueTypeVector},
@@ -247,6 +353,16 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"tan": {
Name: "tan",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"tanh": {
Name: "tanh",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"time": {
Name: "time",
ArgTypes: []ValueType{},

Some files were not shown because too many files have changed in this diff Show More