Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-55224] convert plugin config to scraper config #3

Merged
merged 8 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ linters-settings:
gofmt:
simplify: true
goimports:
local-prefixes: github.com/mattermost/mattermost-starter-template
local-prefixes: github.com/mattermost/mattermost-plugin-metrics
govet:
check-shadowing: true
enable-all: true
Expand Down Expand Up @@ -46,3 +46,13 @@ issues:
linters:
- bodyclose
- scopelint # https://github.com/kyoh86/scopelint/issues/4
# too much false positives, manually disabling for now https://github.com/golangci/golangci-lint/issues/2912
- path: pkg/mod
linters:
- typecheck
- path: net/http
linters:
- typecheck
- path: server
linters:
- typecheck
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/mattermost/mattermost/server/public v0.0.9
github.com/mattermost/mattermost/server/v8 v8.0.0-20231109142113-8bf0c1971415
github.com/pkg/errors v0.9.1
github.com/prometheus/common v0.44.0
github.com/prometheus/prometheus v0.47.2
github.com/stretchr/testify v1.8.4
go.uber.org/mock v0.3.0
Expand Down Expand Up @@ -72,7 +73,6 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/common/sigv4 v0.1.0 // indirect
github.com/prometheus/procfs v0.11.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
Expand Down
19 changes: 19 additions & 0 deletions server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ type configuration struct {
HonorTimestamps *bool
// Option to enable the experimental in-memory metadata storage and append metadata to the WAL.
EnableMetadataStorage *bool
// Scrape interval is the time between polling the /metrics endpoint
ScrapeIntervalSeconds *int
// Screpe timeout tells scraper to give up on the poll for a single scrape attempt
ScrapeTimeoutSeconds *int
}

func (c *configuration) SetDefaults() {
Expand Down Expand Up @@ -61,9 +65,24 @@ func (c *configuration) SetDefaults() {
if c.EnableMetadataStorage == nil {
c.EnableMetadataStorage = model.NewBool(true)
}
if c.ScrapeIntervalSeconds == nil {
c.ScrapeIntervalSeconds = model.NewInt(60)
}
if c.ScrapeTimeoutSeconds == nil {
c.ScrapeTimeoutSeconds = model.NewInt(10)
}
}

func (c *configuration) IsValid() error {
if *c.ScrapeIntervalSeconds < 1 {
return errors.New("scrape interval should be greater than zero")
}
if *c.ScrapeTimeoutSeconds < 1 {
return errors.New("scrape timeout should be greater than zero")
}
if *c.BodySizeLimitBytes < 100 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also impose a max body size limit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we can do that. Ideally it should be unbounded as we are unaware of possible sample size but for our case we can forecast.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have some limit as a config for customers to enforce. We can have some large value as default, but it'd be good to allow customers to set some limit as their preference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add that check for the next PR

return errors.New("openmetrics body size is not realistic, should be greater than 100 bytes")
}
return nil
}

Expand Down
9 changes: 9 additions & 0 deletions server/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"github.com/prometheus/prometheus/model/labels"
)

var (
sampleMutator func(labels.Labels) labels.Labels //nolint:unused
)
41 changes: 41 additions & 0 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ package main

import (
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"

"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/relabel"
"github.com/prometheus/prometheus/scrape"
"github.com/prometheus/prometheus/tsdb"
)

Expand Down Expand Up @@ -54,6 +61,10 @@ func (p *Plugin) OnActivate() error {
p.configuration.SetDefaults()
}

if err = p.configuration.IsValid(); err != nil {
return fmt.Errorf("could not validate config: %w", err)
}

// check if cluster is enabled
if p.isHA() {
// TODO(isacikgoz): get cluster info
Expand All @@ -72,6 +83,36 @@ func (p *Plugin) OnActivate() error {
return fmt.Errorf("could not open target tsdb: %w", err)
}

scrapeInterval := *p.configuration.ScrapeIntervalSeconds
host, port, err := net.SplitHostPort(*appCfg.MetricsSettings.ListenAddress)
if err != nil {
return fmt.Errorf("could not parse the listen address %q", *appCfg.MetricsSettings.ListenAddress)
}
if host == "" {
host = "localhost"
}

// TODO(isacikgoz): Use multiple targets for HA env
lb := labels.NewBuilder(labels.FromMap(map[string]string{
model.AddressLabel: host + ":" + port, // this is used for labeling the source
model.ScrapeIntervalLabel: fmt.Sprintf("%ds", scrapeInterval),
model.ScrapeTimeoutLabel: fmt.Sprintf("%ds", *p.configuration.ScrapeTimeoutSeconds),
}))

lset, origLabels, err := scrape.PopulateLabels(lb, &config.ScrapeConfig{
JobName: "prometheus",
}, true)
if err != nil {
return err
}

target := scrape.NewTarget(lset, origLabels, url.Values{})

// Mutator is being used to apply labels to the metric samples
sampleMutator = func(l labels.Labels) labels.Labels {
agnivade marked this conversation as resolved.
Show resolved Hide resolved
return mutateSampleLabels(l, target, *p.configuration.HonorTimestamps, []*relabel.Config{})
}

return nil
}

Expand Down
60 changes: 60 additions & 0 deletions server/sample.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/relabel"
"github.com/prometheus/prometheus/scrape"
"golang.org/x/exp/slices"
)

func mutateSampleLabels(lset labels.Labels, target *scrape.Target, honor bool, rc []*relabel.Config) labels.Labels {
lb := labels.NewBuilder(lset)

if honor {
agnivade marked this conversation as resolved.
Show resolved Hide resolved
target.LabelsRange(func(l labels.Label) {
if !lset.Has(l.Name) {
lb.Set(l.Name, l.Value)
}
})
} else {
var conflictingExposedLabels []labels.Label
target.LabelsRange(func(l labels.Label) {
existingValue := lset.Get(l.Name)
if existingValue != "" {
conflictingExposedLabels = append(conflictingExposedLabels, labels.Label{Name: l.Name, Value: existingValue})
}
// It is now safe to set the target label.
lb.Set(l.Name, l.Value)
})

if len(conflictingExposedLabels) > 0 {
resolveConflictingExposedLabels(lb, conflictingExposedLabels)
}
}

res := lb.Labels()

if len(rc) > 0 {
res, _ = relabel.Process(res, rc...)
}

return res
}

func resolveConflictingExposedLabels(lb *labels.Builder, conflictingExposedLabels []labels.Label) {
agnivade marked this conversation as resolved.
Show resolved Hide resolved
slices.SortStableFunc(conflictingExposedLabels, func(a, b labels.Label) bool {
return len(a.Name) < len(b.Name)
})

for _, l := range conflictingExposedLabels {
newName := l.Name
for {
newName = model.ExportedLabelPrefix + newName
if lb.Get(newName) == "" {
lb.Set(newName, l.Value)
break
}
}
}
}
44 changes: 44 additions & 0 deletions server/sample_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"testing"

"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"github.com/stretchr/testify/require"
)

func TestResolveConflictingExposedLabels(t *testing.T) {
lb := labels.NewBuilder(labels.FromMap(map[string]string{
model.AddressLabel: "test-address",
model.ScrapeIntervalLabel: "1s",
model.ScrapeTimeoutLabel: "5s",
}))

t.Run("no conflicts", func(t *testing.T) {
lbls := []labels.Label{}
resolveConflictingExposedLabels(lb, lbls)

require.Equal(t, 3, lb.Labels().Len()) // we defined 3 labels they are not conflicting
})

t.Run("conflicting on address label", func(t *testing.T) {
lbls := []labels.Label{
{Name: model.AddressLabel, Value: "new-address"},
}
resolveConflictingExposedLabels(lb, lbls)

require.NotEmpty(t, lb.Get(model.AddressLabel))
require.NotEmpty(t, lb.Get(model.ExportedLabelPrefix+model.AddressLabel))
require.Equal(t, "new-address", lb.Get(model.ExportedLabelPrefix+model.AddressLabel))
})

t.Run("not conflicting, does not exist in the label set", func(t *testing.T) {
lbls := []labels.Label{
{Name: "new-key", Value: "new-address"},
}
resolveConflictingExposedLabels(lb, lbls)

require.Empty(t, lb.Get("new-key"))
})
}