-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[chore] RFC for scraper controller extension #14469
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
Merged
mx-psi
merged 5 commits into
open-telemetry:main
from
axw:rfc-scraper-controller-extensions
Apr 10, 2026
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| # Scraper Controller Extensions | ||
|
|
||
| ## Overview | ||
|
|
||
| This RFC proposes introducing an extension interface for scraper controllers to enable event-driven scraping patterns. | ||
| The current time-based scraper controller will be maintained with an option to disable the timer, and new configuration will allow specifying one or more scraper controller extensions that can trigger scrapes based on external events. | ||
|
|
||
| ## Motivation | ||
|
|
||
| The current scraper controller implementation uses a time-based ticker to periodically invoke scrapers. While this works well for many use cases, there are scenarios where event-driven scraping would be more appropriate, such as: | ||
|
|
||
| 1. **Webhook-driven scrapes**: On-demand profiling or detailed metric collection triggered by alerts (e.g., CPU/memory spikes) | ||
| 2. **Job-queue based scraping**: Horizontal scaling where multiple collector instances consume scrape jobs from a shared queue | ||
| 3. **External scheduling**: Using external schedulers (e.g. cron, Kubernetes CronJobs) to avoid mostly-idle, long-lived processes | ||
|
|
||
| ## Current State | ||
|
|
||
| The scraper controller is implemented in `scraper/scraperhelper/internal/controller/controller.go`: | ||
|
|
||
| - Uses `ControllerConfig` with `CollectionInterval`, `InitialDelay`, and `Timeout` fields | ||
| - Creates a time-based ticker in `startScraping()` that periodically calls the scrape function | ||
| - Scrapers are created during controller initialization and started/shutdown with the controller | ||
| - The controller manages the lifecycle of multiple scrapers and coordinates their execution | ||
|
|
||
| ## Proposed Changes | ||
|
|
||
| We propose to introduce a new extension interface for scraper controllers, enabling flexibility over how scrapes are triggered. | ||
| The timer-based scraping will remain the default behavior but can be disabled via configuration. | ||
|
|
||
| ### 1. Extension Interface | ||
|
|
||
| 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. | ||
| 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) | ||
| } | ||
|
|
||
| // RegistrationHandle provides a way to deregister a scraper from a controller extension | ||
| type RegistrationHandle interface { | ||
|
dmitryax marked this conversation as resolved.
|
||
| // Deregister removes the scraper from the controller extension | ||
| Deregister(ctx context.Context) error | ||
| } | ||
| ``` | ||
|
|
||
| ### 2. Configuration Changes | ||
|
|
||
| Modify `ControllerConfig` to support: | ||
| - Optional timer configuration (can be disabled) | ||
| - List of controller extension IDs | ||
|
|
||
| ```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. | ||
| CollectionInterval time.Duration `mapstructure:"collection_interval"` | ||
|
|
||
| // InitialDelay sets the initial start delay for the scraper timer. | ||
| InitialDelay time.Duration `mapstructure:"initial_delay"` | ||
|
|
||
| // Timeout is an optional value used to set scraper's context deadline. | ||
| Timeout time.Duration `mapstructure:"timeout"` | ||
|
|
||
| // Controllers specifies one or more controller extensions that | ||
| // will control when scrapes are triggered. Extensions must implement | ||
| // scraperhelper.ControllerExtension. | ||
| Controllers []component.ID `mapstructure:"controllers"` | ||
| } | ||
| ``` | ||
|
|
||
| ### 3. Controller Implementation Changes | ||
|
|
||
| Modify `controller.Controller` to: | ||
| - Support disabling the timer when `CollectionInterval <= 0` | ||
| - 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 | ||
|
|
||
| ## Use Cases | ||
|
|
||
| Below are use cases that motivate this change. Some of the use cases describe hypothetical | ||
| scrapers, such scraper-based profiling, that do not currently exist in the OpenTelemetry Collector. | ||
| They are included to illustrate how event-driven scraping could be beneficial. | ||
|
|
||
| ### Use Case 1: Webhook-Driven Scraping | ||
|
|
||
| A collector receives webhook notifications when CPU usage exceeds a threshold and triggers an | ||
| on-demand profiling scrape of a pprof endpoint: | ||
|
|
||
| ```yaml | ||
| extensions: | ||
| webhook_scraper_controller: | ||
|
dmitryax marked this conversation as resolved.
|
||
| endpoint: http://0.0.0.0:8080 | ||
| path: / | ||
|
|
||
| receivers: | ||
| # Hypothetical pprof receiver that scrapes pprof HTTP endpoints | ||
| pprof/cpu_10s: | ||
| endpoint: https://localhost:6060/debug/pprof?seconds=10 | ||
| controllers: | ||
| - webhook_controller | ||
|
|
||
| service: | ||
| extensions: [webhook_scraper_controller] | ||
| pipelines: | ||
| profiles: | ||
| receivers: [pprof/cpu_10s] | ||
| exporters: [debug] | ||
| ``` | ||
|
|
||
| ### Use Case 2: Job Queue Based Scraping | ||
|
|
||
| Multiple collector instances consume scrape jobs from a shared queue: | ||
|
|
||
| ```yaml | ||
| extensions: | ||
| queue_scraper_controller: | ||
| queue_type: redis | ||
| queue_url: redis://queue:6379 | ||
| queue_name: httpcheck-jobs | ||
|
|
||
| receivers: | ||
| httpcheck: | ||
| collection_interval: 0 | ||
| targets: | ||
| - ... | ||
| controllers: | ||
| - queue_controller | ||
| ``` | ||
|
|
||
| ### Use Case 3: External Cron Scheduling (one-shot execution) | ||
|
|
||
| A Kubernetes CronJob runs the collector as a one-shot job that scrapes once and exits, | ||
| avoiding a mostly-idle long-lived process. A similar approach could be used with any | ||
| external scheduler, such as AWS EventBridge or Google Cloud Scheduler. In any case, | ||
| the collector is expected to run, perform the scrape, and then exit. | ||
|
|
||
| Multiple implementation options exist for this use case, and the best approach may | ||
| depend on the specific platform. For example, integrating with AWS EventBridge may | ||
| may involve running the collector as an AWS Lambda function, handling the relevant | ||
| invocation event. | ||
|
|
||
| For plain old cron, or Kubernetes CronJobs, two possible approaches are outlined below. | ||
|
|
||
| #### Option A: new "scrape" CLI command | ||
|
|
||
| ```bash | ||
| # Kubernetes CronJob runs a new command, specifying the pipeline(s) and scraper(s) | ||
| # to run once and then exit. | ||
| otelcol scrape --config=config.yaml metrics hostmetrics | ||
| ``` | ||
|
|
||
| The collector would support a new `scrape` CLI command that: | ||
| - Loads the specified configuration | ||
| - Injects a controller extension into the specified scraper(s) that | ||
| the command can invoke to trigger a scrape | ||
| - Triggers the scraper(s) once via the injected controller extension | ||
| - Exits after the scrape completes | ||
|
|
||
| #### Option B: self-contained, one-shot controller | ||
|
|
||
| ```yaml | ||
| extensions: | ||
| oneshot_controller: | ||
| # Extension that triggers scrape on registration and signals shutdown after completion | ||
|
|
||
| receivers: | ||
| hostmetrics: | ||
| collection_interval: 0 | ||
| controllers: | ||
| - oneshot_controller | ||
| ``` | ||
|
|
||
| The challenge with Option B is that it becomes difficult if not impossible to invoke multiple | ||
| scrapers, as they will start concurrently and independently. Consequently, there needs to be | ||
| some additional coordination mechanism to wait for all scrapers to complete before exiting. | ||
|
|
||
| ## Open Questions | ||
|
|
||
| 1. Should there be a way to pass dynamic configuration from the extension to the scraper, e.g. `targets` for httpcheck? | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is probably better handled with something like receivercreator (e.g. scrapercreator), which dynamically creates one-shot scrapers in response to some event(s). The event payload could then be referenced with variables in the scraper's configuration. |
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.