diff --git a/CHANGELOG.md b/CHANGELOG.md index 13ff316031..d125860954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ Main (unreleased) - In `prometheus.exporter.kafka`, the interpolation table used to compute estimated lag metrics is now pruned on `metadata_refresh_interval` instead of `prune_interval_seconds`. (@wildum) +- Add support for configuring CPU profile's duration scraped by `pyroscope.scrape`. (@hainenber) + ### Bugfixes - Fixed issue with defaults for Beyla component not being applied correctly. (marctc) diff --git a/docs/sources/reference/components/pyroscope.scrape.md b/docs/sources/reference/components/pyroscope.scrape.md index 3365306139..b02e3651dd 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 +`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,10 @@ 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 `profiling_duration`, then `seconds` is assigned the same value as `profiling_duration`. + For example, if you set `scrape_interval` to `"15s"`, then `seconds` defaults to `14s`. + If you set `profiling_duration` to `16s`, then `seconds is set to `16s` regardless of the `scrape_interval` value. 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 5647aea5a1..4a1ac8bbf8 100644 --- a/internal/component/pyroscope/scrape/scrape.go +++ b/internal/component/pyroscope/scrape/scrape.go @@ -21,15 +21,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 = 14 * time.Second ) func init() { @@ -60,6 +62,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. + ProfilingDuration time.Duration `river:"profiling_duration,attr,optional"` // todo(ctovena): add support for limits. // // An uncompressed response body larger than this many bytes will cause the @@ -192,11 +196,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, + ProfilingDuration: defaultProfilingDuration, } } @@ -218,6 +223,9 @@ 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 && arg.ProfilingDuration.Seconds() <= 1 { + return fmt.Errorf("profiling_duration must be larger then 1 second 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 e08120f1c8..a4b4226022 100644 --- a/internal/component/pyroscope/scrape/scrape_test.go +++ b/internal/component/pyroscope/scrape/scrape_test.go @@ -151,6 +151,16 @@ func TestUnmarshalConfig(t *testing.T) { `, expectedErr: "scrape_interval must be at least 2 seconds when using delta profiling", }, + "invalid cpu profiling_duration": { + in: ` + targets = [] + forward_to = null + scrape_timeout = "1s" + scrape_interval = "10s" + profiling_duration = "1s" + `, + expectedErr: "profiling_duration must be larger then 1 second when using delta profiling", + }, "allow short scrape_intervals without delta": { in: ` targets = [] diff --git a/internal/component/pyroscope/scrape/target.go b/internal/component/pyroscope/scrape/target.go index 736d75b43f..a05bbfb0b0 100644 --- a/internal/component/pyroscope/scrape/target.go +++ b/internal/component/pyroscope/scrape/target.go @@ -406,7 +406,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.ProfilingDuration != defaultProfilingDuration { + seconds = (cfg.ProfilingDuration) / 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 b4d6fd2aa6..ba8231a6a9 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" @@ -236,3 +237,65 @@ func Test_targetsFromGroup(t *testing.T) { require.Equal(t, expected, active) require.Empty(t, dropped) } + +func Test_targetsFromGroup_withSpecifiedProfilingDuration(t *testing.T) { + args := NewDefaultArguments() + args.ProfilingConfig.Block.Enabled = false + args.ProfilingConfig.Goroutine.Enabled = false + args.ProfilingConfig.Mutex.Enabled = false + args.ProfilingDuration = 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) +}