-
Notifications
You must be signed in to change notification settings - Fork 38
feat(automation): integrate .NET instrumentation watcher #421
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
Open
Pittu-Sharma
wants to merge
27
commits into
open-telemetry:main
Choose a base branch
from
Pittu-Sharma:feat/dotnet-instrumentation-watcher
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
c26bddb
feat: integrate .NET instrumentation watcher
Pittu-Sharma adf5fc4
style: run prettier format check fixes
Pittu-Sharma 9daeef2
Refactor .NET instrumentation watcher to use NuGet V3 Search API
Pittu-Sharma bc9f0fb
trigger ci: re-run workflows
Pittu-Sharma 1b14238
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 8b02341
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 1628d32
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 1f360f7
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma c27f3c7
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 5091e8b
Address mentor feedback: Use NuGet service index, include prerelease,…
Pittu-Sharma 8d3b2fd
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 7e7065b
fix(dotnet-watcher): address mentor review feedback
Pittu-Sharma 6ab4f7b
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 8e46aa2
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 917a264
fix(.net-watcher): correct versioning and filter deprecated packages
Pittu-Sharma 1236676
Merge branch 'feat/dotnet-instrumentation-watcher' of https://github.…
Pittu-Sharma 5945063
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 13190d8
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 880f805
refactor(api): address copilot feedback on resilience and caching
Pittu-Sharma 462a137
chore: remove unused files, revert glow badge changes, and fix result…
Pittu-Sharma b7ca795
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 46a7bd5
Merge branch 'main' from upstream to resolve conflicts
Pittu-Sharma fcf9570
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma fd28c6d
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 779e4c9
Merge branch 'main' into feat/dotnet-instrumentation-watcher
Pittu-Sharma 1b17c11
chore: remove accidental files from other branches in PR #421
Pittu-Sharma ed77be6
chore: revert all accidental UI changes to match main
Pittu-Sharma 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
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,31 @@ | ||
| # .NET Integration Roadmap | ||
|
|
||
| This document outlines the proposed strategy and roadmap for fully integrating the `.NET` ecosystem | ||
| into the OpenTelemetry Ecosystem Explorer. | ||
|
|
||
| ## Phase 1: Metadata Collection (Completed) | ||
|
|
||
| - [x] Create a `dotnet-instrumentation-watcher` component within `ecosystem-automation`. | ||
| - [x] Design a programmatic scraper to dynamically fetch `.NET` repositories | ||
| (`open-telemetry/opentelemetry-dotnet-contrib`) and parse `.csproj` structures since | ||
| centralized catalog APIs do not exist. | ||
| - [x] Synchronize this data into `ecosystem-registry/dotnet/` with versioned snapshot capability. | ||
| - [x] Ensure structural data uses proper classifications (e.g., `instrumentation`, `exporter`, | ||
| `extension`). | ||
|
|
||
| ## Phase 2: Database Integration (Upcoming) | ||
|
|
||
| - [ ] Extend `explorer-db-builder` to support `.NET` specific processing logic alongside Java Agent | ||
| logic. | ||
| - [ ] Create a `dotnet` specific database writer inside | ||
| `explorer-db-builder/src/explorer_db_builder/database_writer.py`. | ||
| - [ ] Transform `modules` lists generated by the watcher into Explorer-compatible schema elements | ||
| for the SQLite frontend ingest payload. | ||
|
|
||
| ## Phase 3: Frontend Implementation (Upcoming) | ||
|
|
||
| - [ ] Add a new navigation card on the `ecosystem-explorer` Home Page mapping to `/dotnet`. | ||
| - [ ] Create `.NET` specific exploration React components in `ecosystem-explorer/src/pages/dotnet`. | ||
| - [ ] Implement UI data hooks in `use-instrumentations.ts` to fetch and render `.NET` libraries. | ||
| - [ ] Audit accessibility (`aria-labels`, focus indicators) for any new `.NET` specific UI | ||
| components introduced. | ||
14 changes: 0 additions & 14 deletions
14
ecosystem-automation/configuration-watcher/tests/__init__.py
This file was deleted.
Oops, something went wrong.
30 changes: 30 additions & 0 deletions
30
ecosystem-automation/dotnet-instrumentation-watcher/pyproject.toml
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,30 @@ | ||
| [project] | ||
| name = "dotnet-instrumentation-watcher" | ||
| version = "0.1.0" | ||
| description = "Automation tool for watching and collecting OpenTelemetry .NET instrumentation metadata" | ||
| requires-python = ">=3.11" | ||
| dependencies = [ | ||
| "PyYAML>=6.0.1", | ||
| "requests>=2.31.0", | ||
| "semantic-version>=2.10.0", | ||
| "watcher-common", | ||
| ] | ||
|
|
||
| [project.scripts] | ||
| dotnet-instrumentation-watcher = "dotnet_instrumentation_watcher.main:main" | ||
|
|
||
| [project.optional-dependencies] | ||
| dev = [ | ||
| "pytest>=8.0.0", | ||
| "pytest-cov>=4.1.0", | ||
| ] | ||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [tool.uv.sources] | ||
| watcher-common = { workspace = true } | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["src/dotnet_instrumentation_watcher"] |
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
182 changes: 182 additions & 0 deletions
182
...mation/dotnet-instrumentation-watcher/src/dotnet_instrumentation_watcher/dotnet_client.py
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,182 @@ | ||
| # Copyright The OpenTelemetry Authors | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| """NuGet API client for fetching .NET instrumentation data.""" | ||
|
|
||
| import logging | ||
| from typing import Any, Dict, List | ||
|
|
||
| import requests | ||
| from requests.adapters import HTTPAdapter | ||
| from urllib3 import Retry | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class NuGetAPIError(Exception): | ||
| """Custom exception for NuGet API errors.""" | ||
|
|
||
| pass | ||
|
|
||
|
|
||
| class DotNetInstrumentationClient: | ||
| """Client for fetching .NET instrumentation metadata from NuGet.""" | ||
|
|
||
| SERVICE_INDEX_URL = "https://api.nuget.org/v3/index.json" | ||
| OWNER = "OpenTelemetry" | ||
| TIMEOUT = 30 | ||
|
|
||
| def __init__(self): | ||
| """Initialize the client.""" | ||
| self._session = requests.Session() | ||
| self._search_url = None | ||
|
|
||
| retry_strategy = Retry( | ||
| total=3, | ||
| backoff_factor=1, | ||
| status_forcelist=[429, 500, 502, 503, 504], | ||
| ) | ||
|
|
||
| adapter = HTTPAdapter(max_retries=retry_strategy) | ||
| self._session.mount("https://", adapter) | ||
|
|
||
| def _get_search_url(self) -> str: | ||
| """Resolve the search URL from the NuGet service index. | ||
|
|
||
| Raises: | ||
| NuGetAPIError: If the service index cannot be fetched or does not | ||
| contain a SearchQueryService resource. | ||
| """ | ||
| if self._search_url: | ||
| return self._search_url | ||
|
|
||
| try: | ||
| response = self._session.get(self.SERVICE_INDEX_URL, timeout=self.TIMEOUT) | ||
| response.raise_for_status() | ||
| index_data = response.json() | ||
| except requests.RequestException as e: | ||
| raise NuGetAPIError(f"Error fetching NuGet service index: {e}") from e | ||
|
|
||
| for resource in index_data.get("resources", []): | ||
| if resource.get("@type") == "SearchQueryService": | ||
| self._search_url = resource["@id"] | ||
| return self._search_url | ||
|
|
||
| raise NuGetAPIError("NuGet service index did not contain a SearchQueryService resource") | ||
|
|
||
| def fetch_instrumentation_list(self) -> Dict[str, Any]: | ||
| """ | ||
| Fetch instrumentation list by querying NuGet for packages owned by OpenTelemetry. | ||
|
|
||
| The top-level ``version`` field in each search result entry is the latest | ||
| version of the package as reported by NuGet — no local sorting is needed. | ||
| """ | ||
| all_packages = self._fetch_all_packages_by_owner(self.OWNER) | ||
| modules = [] | ||
|
|
||
| for pkg in all_packages: | ||
| package_id = pkg.get("id", "") | ||
|
|
||
| # Skip packages flagged as deprecated by NuGet (includes Contrib packages). | ||
| if pkg.get("deprecation"): | ||
| logger.info(f" Skipping deprecated package: {package_id}") | ||
| continue | ||
|
|
||
| # The top-level "version" field is the latest version returned by the | ||
| # NuGet search API — rely on the server ordering rather than sorting locally. | ||
| version = pkg.get("version", "") | ||
| description = pkg.get("description", "") | ||
|
|
||
| # Filter and classify packages | ||
| if "Instrumentation" in package_id: | ||
| component_type = "instrumentation" | ||
| elif "Exporter" in package_id: | ||
| component_type = "exporter" | ||
| elif "Extensions" in package_id or "Resources" in package_id or "Sampler" in package_id: | ||
| component_type = "extension" | ||
| else: | ||
| # Skip core and unclassified packages. | ||
| continue | ||
|
|
||
| modules.append( | ||
| { | ||
| "name": package_id, | ||
| "description": description or f"{package_id} for OpenTelemetry", | ||
| "type": component_type, | ||
| "version": version, | ||
| } | ||
| ) | ||
|
|
||
| # Sort by name for deterministic registry output. | ||
| modules.sort(key=lambda x: x["name"]) | ||
|
|
||
| return {"modules": modules} | ||
|
|
||
| def get_core_version(self) -> str: | ||
| """Get the latest stable version of the core OpenTelemetry package. | ||
|
|
||
| This is used as the 'ecosystem version' for the registry. | ||
|
|
||
| Raises: | ||
| NuGetAPIError: If the version cannot be determined. | ||
| """ | ||
| params = { | ||
| "q": "PackageId:OpenTelemetry", | ||
| "prerelease": "false", | ||
| "take": 1, | ||
| } | ||
| try: | ||
| search_url = self._get_search_url() | ||
| response = self._session.get(search_url, params=params, timeout=self.TIMEOUT) | ||
| response.raise_for_status() | ||
| data = response.json() | ||
| results = data.get("data", []) | ||
| if not results: | ||
| raise NuGetAPIError("No results returned for core OpenTelemetry package") | ||
| return results[0]["version"] | ||
| except (KeyError, IndexError) as e: | ||
| raise NuGetAPIError(f"Unexpected response shape fetching core version: {e}") from e | ||
| except requests.RequestException as e: | ||
| raise NuGetAPIError(f"Error fetching core version: {e}") from e | ||
|
|
||
| def _fetch_all_packages_by_owner(self, owner: str) -> List[Dict[str, Any]]: | ||
| """Fetch all packages for a specific owner using pagination.""" | ||
| packages = [] | ||
| skip = 0 | ||
| take = 20 | ||
|
|
||
| while True: | ||
| params = { | ||
| "q": f"owner:{owner}", | ||
| "prerelease": "true", | ||
| "skip": skip, | ||
| "take": take, | ||
| } | ||
| try: | ||
| search_url = self._get_search_url() | ||
| response = self._session.get(search_url, params=params, timeout=self.TIMEOUT) | ||
| response.raise_for_status() | ||
| data = response.json() | ||
|
|
||
| batch = data.get("data", []) | ||
| packages.extend(batch) | ||
|
|
||
| if len(batch) < take: | ||
| break | ||
|
|
||
| skip += take | ||
| except requests.RequestException as e: | ||
| raise NuGetAPIError(f"Error fetching packages from NuGet: {e}") from e | ||
|
|
||
| return packages |
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.