Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e126197
[scraperhelper] Add extension-based scraper controller support
axw Apr 15, 2026
0821222
Merge branch 'main' into extensionscrapercontroller
axw May 4, 2026
7d3f34f
Merge branch 'main' into extensionscrapercontroller
axw May 27, 2026
de565a5
Merge commit '082122255168501b25cac9ad44eba6684cd4b224' into extensio…
axw May 27, 2026
d278cbe
Various changes
axw May 27, 2026
6441cb3
Tidy up
axw May 27, 2026
77d98b0
DeregisterFunc waits for in-flight scrapes
axw May 27, 2026
a087895
scraperhelper: fix review issues in scraper controller extension
axw May 27, 2026
c1c196b
scraperhelper: fix nil DeregisterFunc panic, race in mock, and parall…
axw May 27, 2026
d725ed1
Fix formatting
axw May 28, 2026
0b66ebd
Revert error message
axw May 28, 2026
ca5b483
One more attempt...
axw May 28, 2026
b978909
Merge branch 'main' into extensionscrapercontroller
axw May 28, 2026
500ab18
Merge branch 'main' into extensionscrapercontroller
axw May 29, 2026
4565f08
make gotidy
axw May 29, 2026
7b69d32
Merge branch 'main' into extensionscrapercontroller
axw Jun 17, 2026
935edc6
Merge branch 'main' into extensionscrapercontroller
axw Jun 23, 2026
b626ef4
Increase test coverage
axw Jun 23, 2026
1383327
Fix lint
axw Jun 23, 2026
da5e6f6
Merge branch 'main' into extensionscrapercontroller
axw Jun 24, 2026
b8e6d91
Merge branch 'main' into extensionscrapercontroller
axw Jul 6, 2026
3dd829d
Only shut down scrapers on controller shutdown
axw Jul 6, 2026
808e47d
Merge branch 'main' into extensionscrapercontroller
axw Jul 8, 2026
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
25 changes: 25 additions & 0 deletions .chloggen/extensionscrapercontroller.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp)
component: pkg/scraperhelper

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add ControllerExtension interface for extensible scraper controllers

# One or more tracking issues or pull requests related to the change
issues: [12449]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
2 changes: 2 additions & 0 deletions cmd/mdatagen/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ require (
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.155.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.155.0 // indirect
go.opentelemetry.io/collector/extension v1.61.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.155.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.155.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.155.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.155.0 // indirect
Expand Down
3 changes: 2 additions & 1 deletion cmd/mdatagen/internal/samplescraper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ This scraper is used for testing purposes to check the output of mdatagen.
## Configuration
| Setting | Type | Default | Required | Description |
| ------- | ---- | ------- | -------- | ----------- |
| `collection_interval` | duration | 1m | no | Sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval. |
| `collection_interval` | duration | 1m | no | Sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval. Must be positive, or zero to disable timer-based scraping (requires controllers to be non-empty). |
| `component` | string | | no | Identifies the scraper, used for telemetry and logging. |
| `controllers` | []string | | no | An optional list of extension IDs that control when scrapes occur. When specified, extensions can trigger scrapes based on external events. If controllers is non-empty, collection_interval may be zero to disable timer-based scraping entirely. |
| `initial_delay` | duration | 1s | no | Sets the initial start delay for the scraper, any non positive value is assumed to be immediately. |
| `job_name` | string | test_job | **yes** | Name of the scrape job, used to identify the source in telemetry. |
| `log_level` | string (one of: debug, info, warn, error) | info | no | Logging level for the scraper. |
Expand Down
9 changes: 8 additions & 1 deletion cmd/mdatagen/internal/samplescraper/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@
"type": "object",
"properties": {
"collection_interval": {
"description": "Sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval.",
"description": "Sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval. Must be positive, or zero to disable timer-based scraping (requires controllers to be non-empty).",
"type": "string",
"default": "1m",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"controllers": {
"description": "An optional list of extension IDs that control when scrapes occur. When specified, extensions can trigger scrapes based on external events. If controllers is non-empty, collection_interval may be zero to disable timer-based scraping entirely.",
"type": "array",
"items": {
"type": "string"
}
},
"initial_delay": {
"description": "Sets the initial start delay for the scraper, any non positive value is assumed to be immediately.",
"type": "string",
Expand Down
49 changes: 20 additions & 29 deletions docs/rfcs/scraper-controller-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,30 @@ The timer-based scraping will remain the default behavior but can be disabled vi
Create a new extension interface (e.g. in `extension/extensionscrapercontroller`):

```golang
// ControllerExtension is an extension that provides a means of registering scrapers,
// and giving the extension control over when registered scrapers are invoked.
// ControllerExtension is an extension that controls when scraper-based
// receivers invoke their scrapers.
type ControllerExtension interface {
extension.Extension

// RegisterScraper registers a scraper with this controller extension.
// The extension will invoke the provided scrape function according to its implementation.
// Returns a registration handle that can be used to deregister the scraper.
RegisterScraper(ctx context.Context, scraperID component.ID, scrapeFunc func(context.Context) error) (RegistrationHandle, error)
// RegisterScraper registers a scraper with the extension. The extension
// will call the provided ScrapeFunc when it determines a scrape should
// occur. The returned DeregisterFunc must be called during shutdown to
// deregister the scraper from the controller.
//
// Implementations may call the ScrapeFunc concurrently. DeregisterFunc
// must not return until all in-flight calls to ScrapeFunc have completed,
// and must guarantee that ScrapeFunc will not be called again after it
// returns.
RegisterScraper(context.Context, ScrapeFunc) (DeregisterFunc, error)
}

// RegistrationHandle provides a way to deregister a scraper from a controller extension
type RegistrationHandle interface {
// Deregister removes the scraper from the controller extension
Deregister(ctx context.Context) error
}
```

We will define a `DeregisterFunc` function type implementing `RegistrationHandle` for convenience:
// ScrapeFunc is a function that is registered with
// ControllerExtension.RegisterScraper in order to perform a scrape.
type ScrapeFunc func(context.Context) error

```golang
// DeregisterFunc implements RegistrationHandle using a simple function.
//
// If the function value is nil, the method call will be a no-op.
// DeregisterFunc is a function returned by ControllerExtension.RegisterScraper
// and is used to deregister the scraper during shutdown.
type DeregisterFunc func(ctx context.Context) error

// Deregister calls the underlying function to deregister the scraper.
func (f DeregisterFunc) Deregister(ctx context.Context) error {
if f == nil {
return nil
}
return f(ctx)
}
```

### 2. Configuration Changes
Expand All @@ -76,8 +67,8 @@ Modify `ControllerConfig` to support:
```golang
type ControllerConfig struct {
// CollectionInterval sets how frequently the scraper should be called.
// If zero or negative, the timer-based scraping is disabled.
// At least one controller extension must be configured if timer is disabled.
// Must be positive, or zero to disable timer-based scraping. If zero,
// at least one controller extension must be configured.
CollectionInterval time.Duration `mapstructure:"collection_interval"`

// InitialDelay sets the initial start delay for the scraper timer.
Expand All @@ -96,7 +87,7 @@ type ControllerConfig struct {
### 3. Controller Implementation Changes

Modify `controller.Controller` to:
- Support disabling the timer when `CollectionInterval <= 0`
- Support disabling the timer when `CollectionInterval == 0` (negative values remain invalid)
- Register scrapers with configured controller extensions during `Start()`
- Deregister scrapers during `Shutdown()`
- Validate that at least one controller extension is configured if timer is disabled
Expand Down
6 changes: 6 additions & 0 deletions extension/xextension/extensionscrapercontroller/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package extensionscrapercontroller defines the interface for extensions
// that control when scraper-based receivers invoke their scrapers.
package extensionscrapercontroller // import "go.opentelemetry.io/collector/extension/xextension/extensionscrapercontroller"
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package extensionscrapercontroller // import "go.opentelemetry.io/collector/extension/xextension/extensionscrapercontroller"

import (
"context"

"go.opentelemetry.io/collector/extension"
)

// ControllerExtension is an extension that controls when scraper-based
// receivers invoke their scrapers.
type ControllerExtension interface {
extension.Extension

// RegisterScraper registers a scraper with the extension. The extension
// will call the provided ScrapeFunc when it determines a scrape should
// occur. The returned DeregisterFunc must be called during shutdown to
// deregister the scraper from the controller.
//
// When err is nil, the returned DeregisterFunc must not be nil.
//
// Implementations may call the ScrapeFunc concurrently. DeregisterFunc
// must not return until all in-flight calls to ScrapeFunc have completed,
// and must guarantee that ScrapeFunc will not be called again after it
// returns.
RegisterScraper(context.Context, ScrapeFunc) (DeregisterFunc, error)
}

// ScrapeFunc is a function that is registered with
// ControllerExtension.RegisterScraper in order to perform a scrape.
type ScrapeFunc func(context.Context) error

// DeregisterFunc is a function returned by ControllerExtension.RegisterScraper
// and is used to deregister the scraper during shutdown.
type DeregisterFunc func(ctx context.Context) error
9 changes: 8 additions & 1 deletion scraper/scraperhelper/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@
"type": "object",
"properties": {
"collection_interval": {
"description": "Sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval.",
"description": "Sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval. Must be positive, or zero to disable timer-based scraping (requires controllers to be non-empty).",
"type": "string",
"default": "1m",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"controllers": {
"description": "An optional list of extension IDs that control when scrapes occur. When specified, extensions can trigger scrapes based on external events. If controllers is non-empty, collection_interval may be zero to disable timer-based scraping entirely.",
"type": "array",
"items": {
"type": "string"
}
},
"initial_delay": {
"description": "Sets the initial start delay for the scraper, any non positive value is assumed to be immediately.",
"type": "string",
Expand Down
8 changes: 7 additions & 1 deletion scraper/scraperhelper/config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ $defs:
type: object
properties:
collection_interval:
description: CollectionInterval sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval.
description: CollectionInterval sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval. Must be positive, or zero to disable timer-based scraping (requires Controllers to be non-empty).
type: string
x-customType: time.Duration
format: duration
controllers:
description: Controllers is a list of extension IDs that control when scrapes occur. When specified, extensions can trigger scrapes based on external events. If Controllers is non-empty, CollectionInterval may be zero to disable timer-based scraping entirely.
type: array
items:
type: string
x-customType: go.opentelemetry.io/collector/component.ID
initial_delay:
description: InitialDelay sets the initial start delay for the scraper, any non positive value is assumed to be immediately.
type: string
Expand Down
Loading
Loading