diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eec60ffb1..21037ba077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ Main (unreleased) - Added `scrape_protocols` option to `prometheus.scrape`, which allows to control the preferred order of scrape protocols. (@thampiotr) + +- Add support for configuring CPU profile's duration scraped by `pyroscope.scrape`. (@hainenber) ### Bugfixes diff --git a/docs/sources/reference/components/pyroscope.scrape.md b/docs/sources/reference/components/pyroscope.scrape.md index 3365306139..422e79da1b 100644 --- a/docs/sources/reference/components/pyroscope.scrape.md +++ b/docs/sources/reference/components/pyroscope.scrape.md @@ -65,6 +65,7 @@ Name | Type | Description `params` | `map(list(string))` | A set of query parameters with which the target is scraped. | | no `scrape_interval` | `duration` | How frequently to scrape the targets of this scrape configuration. | `"15s"` | no `scrape_timeout` | `duration` | The timeout for scraping targets of this configuration. Must be larger than `scrape_interval`. | `"18s"` | no +`delta_profiling_duration`| `duration` | The duration for a delta profiling to be scraped. Must be larger than 1 second. | `"14s"` | no `scheme` | `string` | The URL scheme with which to fetch metrics from targets. | `"http"` | no `bearer_token_file` | `string` | File containing a bearer token to authenticate with. | | no `bearer_token` | `secret` | Bearer token to authenticate with. | | no @@ -395,8 +396,11 @@ When the `delta` argument is `false`, the [pprof][] HTTP query will be instantan When the `delta` argument is `true`: * The [pprof][] HTTP query will run for a certain amount of time. * A `seconds` parameter is automatically added to the HTTP request. -* The `seconds` used will be equal to `scrape_interval - 1`. - For example, if `scrape_interval` is `"15s"`, `seconds` will be 14 seconds. +* The default value for the `seconds` query parameter is `scrape_interval - 1`. + If you set `delta_profiling_duration`, then `seconds` is assigned the same value as `delta_profiling_duration`. + However, the `delta_profiling_duration` cannot be larger than `scrape_interval`. + For example, if you set `scrape_interval` to `"15s"`, then `seconds` defaults to `14s` + If you set `delta_profiling_duration` to `16s`, then `scrape_interval` must be set to at least `17s`. If the HTTP endpoint is `/debug/pprof/profile`, then the HTTP query will become `/debug/pprof/profile?seconds=14` ## Exported fields diff --git a/internal/component/pyroscope/scrape/scrape.go b/internal/component/pyroscope/scrape/scrape.go index 504ee6ae6f..34cbf5faa1 100644 --- a/internal/component/pyroscope/scrape/scrape.go +++ b/internal/component/pyroscope/scrape/scrape.go @@ -24,15 +24,17 @@ import ( ) const ( - pprofMemory string = "memory" - pprofBlock string = "block" - pprofGoroutine string = "goroutine" - pprofMutex string = "mutex" - pprofProcessCPU string = "process_cpu" - pprofFgprof string = "fgprof" - pprofGoDeltaProfMemory string = "godeltaprof_memory" - pprofGoDeltaProfBlock string = "godeltaprof_block" - pprofGoDeltaProfMutex string = "godeltaprof_mutex" + pprofMemory string = "memory" + pprofBlock string = "block" + pprofGoroutine string = "goroutine" + pprofMutex string = "mutex" + pprofProcessCPU string = "process_cpu" + pprofFgprof string = "fgprof" + pprofGoDeltaProfMemory string = "godeltaprof_memory" + pprofGoDeltaProfBlock string = "godeltaprof_block" + pprofGoDeltaProfMutex string = "godeltaprof_mutex" + defaultScrapeInterval time.Duration = 15 * time.Second + defaultProfilingDuration time.Duration = defaultScrapeInterval - 1*time.Second ) func init() { @@ -63,6 +65,8 @@ type Arguments struct { ScrapeTimeout time.Duration `alloy:"scrape_timeout,attr,optional"` // The URL scheme with which to fetch metrics from targets. Scheme string `alloy:"scheme,attr,optional"` + // The duration for a profile to be scrapped. + DeltaProfilingDuration time.Duration `alloy:"delta_profiling_duration,attr,optional"` // todo(ctovena): add support for limits. // // An uncompressed response body larger than this many bytes will cause the @@ -195,11 +199,12 @@ var DefaultArguments = NewDefaultArguments() // NewDefaultArguments create the default settings for a scrape job. func NewDefaultArguments() Arguments { return Arguments{ - Scheme: "http", - HTTPClientConfig: component_config.DefaultHTTPClientConfig, - ScrapeInterval: 15 * time.Second, - ScrapeTimeout: 10 * time.Second, - ProfilingConfig: DefaultProfilingConfig, + Scheme: "http", + HTTPClientConfig: component_config.DefaultHTTPClientConfig, + ScrapeInterval: 15 * time.Second, + ScrapeTimeout: 10 * time.Second, + ProfilingConfig: DefaultProfilingConfig, + DeltaProfilingDuration: defaultProfilingDuration, } } @@ -221,6 +226,14 @@ func (arg *Arguments) Validate() error { if target.Enabled && target.Delta && arg.ScrapeInterval.Seconds() < 2 { return fmt.Errorf("scrape_interval must be at least 2 seconds when using delta profiling") } + if target.Enabled && target.Delta { + if arg.DeltaProfilingDuration.Seconds() <= 1 { + return fmt.Errorf("delta_profiling_duration must be larger than 1 second when using delta profiling") + } + if arg.DeltaProfilingDuration.Seconds() > arg.ScrapeInterval.Seconds()-1 { + return fmt.Errorf("delta_profiling_duration must be at least 1 second smaller than scrape_interval when using delta profiling") + } + } } // We must explicitly Validate because HTTPClientConfig is squashed and it won't run otherwise diff --git a/internal/component/pyroscope/scrape/scrape_test.go b/internal/component/pyroscope/scrape/scrape_test.go index d3dd5b3bcc..31d960da02 100644 --- a/internal/component/pyroscope/scrape/scrape_test.go +++ b/internal/component/pyroscope/scrape/scrape_test.go @@ -160,6 +160,26 @@ func TestUnmarshalConfig(t *testing.T) { `, expectedErr: "scrape_interval must be at least 2 seconds when using delta profiling", }, + "invalid cpu delta_profiling_duration": { + in: ` + targets = [] + forward_to = null + scrape_timeout = "1s" + scrape_interval = "10s" + delta_profiling_duration = "1s" + `, + expectedErr: "delta_profiling_duration must be larger than 1 second when using delta profiling", + }, + "erroneous cpu delta_profiling_duration": { + in: ` + targets = [] + forward_to = null + scrape_timeout = "1s" + scrape_interval = "10s" + delta_profiling_duration = "12s" + `, + expectedErr: "delta_profiling_duration must be at least 1 second smaller than scrape_interval when using delta profiling", + }, "allow short scrape_intervals without delta": { in: ` targets = [] @@ -184,7 +204,8 @@ func TestUnmarshalConfig(t *testing.T) { targets = [] forward_to = null scrape_timeout = "5s" - scrape_interval = "2s" + scrape_interval = "3s" + delta_profiling_duration = "2s" bearer_token = "token" bearer_token_file = "/path/to/file.token" `, diff --git a/internal/component/pyroscope/scrape/target.go b/internal/component/pyroscope/scrape/target.go index 127f90c96d..c90144e4c1 100644 --- a/internal/component/pyroscope/scrape/target.go +++ b/internal/component/pyroscope/scrape/target.go @@ -420,7 +420,11 @@ func targetsFromGroup(group *targetgroup.Group, cfg Arguments, targetTypes map[s } if pcfg, found := targetTypes[profType]; found && pcfg.Delta { - params.Add("seconds", strconv.Itoa(int((cfg.ScrapeInterval)/time.Second)-1)) + seconds := (cfg.ScrapeInterval)/time.Second - 1 + if cfg.DeltaProfilingDuration != defaultProfilingDuration { + seconds = (cfg.DeltaProfilingDuration) / time.Second + } + params.Add("seconds", strconv.Itoa(int(seconds))) } targets = append(targets, NewTarget(lbls, origLabels, params)) } diff --git a/internal/component/pyroscope/scrape/target_test.go b/internal/component/pyroscope/scrape/target_test.go index 9895e22c72..f7e825ff3a 100644 --- a/internal/component/pyroscope/scrape/target_test.go +++ b/internal/component/pyroscope/scrape/target_test.go @@ -4,6 +4,7 @@ import ( "net/url" "sort" "testing" + "time" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" @@ -283,3 +284,65 @@ func Test_NewTarget_godeltaprof(t *testing.T) { require.NotEqual(t, withGodeltaprof.allLabels, withoutGodeltaprof.allLabels) require.Equal(t, withGodeltaprof.publicLabels, withoutGodeltaprof.publicLabels) } + +func Test_targetsFromGroup_withSpecifiedDeltaProfilingDuration(t *testing.T) { + args := NewDefaultArguments() + args.ProfilingConfig.Block.Enabled = false + args.ProfilingConfig.Goroutine.Enabled = false + args.ProfilingConfig.Mutex.Enabled = false + args.DeltaProfilingDuration = 20 * time.Second + + active, dropped, err := targetsFromGroup(&targetgroup.Group{ + Targets: []model.LabelSet{ + {model.AddressLabel: "localhost:9090"}, + }, + Labels: model.LabelSet{ + "foo": "bar", + }, + }, args, args.ProfilingConfig.AllTargets()) + expected := []*Target{ + // unspecified + NewTarget( + labels.FromMap(map[string]string{ + model.AddressLabel: "localhost:9090", + serviceNameLabel: "unspecified", + model.MetricNameLabel: pprofMemory, + ProfilePath: "/debug/pprof/allocs", + model.SchemeLabel: "http", + "foo": "bar", + "instance": "localhost:9090", + }), + labels.FromMap(map[string]string{ + model.AddressLabel: "localhost:9090", + model.MetricNameLabel: pprofMemory, + ProfilePath: "/debug/pprof/allocs", + model.SchemeLabel: "http", + "foo": "bar", + }), + url.Values{}), + NewTarget( + labels.FromMap(map[string]string{ + model.AddressLabel: "localhost:9090", + serviceNameLabel: "unspecified", + model.MetricNameLabel: pprofProcessCPU, + ProfilePath: "/debug/pprof/profile", + model.SchemeLabel: "http", + "foo": "bar", + "instance": "localhost:9090", + }), + labels.FromMap(map[string]string{ + model.AddressLabel: "localhost:9090", + model.MetricNameLabel: pprofProcessCPU, + ProfilePath: "/debug/pprof/profile", + model.SchemeLabel: "http", + "foo": "bar", + }), + url.Values{"seconds": []string{"20"}}), + } + + require.NoError(t, err) + sort.Sort(Targets(active)) + sort.Sort(Targets(expected)) + require.Equal(t, expected, active) + require.Empty(t, dropped) +}