SERVER-120535: Dump Merge Queue Metrics to Honeycomb (#48729)
GitOrigin-RevId: 4a4c2d17c9929eae8c764fb4088682994a9814a0
This commit is contained in:
@@ -535,5 +535,17 @@ py_binary(
|
||||
"requests",
|
||||
group = "core",
|
||||
),
|
||||
dependency(
|
||||
"opentelemetry-api",
|
||||
group = "testing",
|
||||
),
|
||||
dependency(
|
||||
"opentelemetry-sdk",
|
||||
group = "testing",
|
||||
),
|
||||
dependency(
|
||||
"opentelemetry-exporter-otlp-proto-http",
|
||||
group = "testing",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -10,9 +10,101 @@ from zoneinfo import ZoneInfo
|
||||
|
||||
import requests
|
||||
|
||||
# Optional OTEL imports for Honeycomb integration
|
||||
try:
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
|
||||
OTEL_AVAILABLE = True
|
||||
except ImportError:
|
||||
OTEL_AVAILABLE = False
|
||||
|
||||
EST = ZoneInfo("America/New_York")
|
||||
CACHE_FILE = Path.home() / ".github_merge_queue_metrics.json"
|
||||
|
||||
# Honeycomb OTEL endpoint
|
||||
HONEYCOMB_OTEL_ENDPOINT = "https://api.honeycomb.io/v1/traces"
|
||||
|
||||
|
||||
def setup_otel_tracer(honeycomb_api_key, honeycomb_dataset):
|
||||
"""Set up OpenTelemetry tracer with Honeycomb HTTP exporter."""
|
||||
if not OTEL_AVAILABLE:
|
||||
print(
|
||||
"OpenTelemetry is not available. "
|
||||
"Install opentelemetry packages to enable Honeycomb export."
|
||||
)
|
||||
return None
|
||||
|
||||
resource = Resource(attributes={SERVICE_NAME: "github-merge-queue-metrics"})
|
||||
|
||||
# Configure OTLP HTTP exporter for Honeycomb
|
||||
headers = {
|
||||
"x-honeycomb-team": honeycomb_api_key,
|
||||
"x-honeycomb-dataset": honeycomb_dataset,
|
||||
}
|
||||
|
||||
exporter = OTLPSpanExporter(
|
||||
endpoint=HONEYCOMB_OTEL_ENDPOINT,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
provider = TracerProvider(resource=resource)
|
||||
processor = BatchSpanProcessor(exporter)
|
||||
provider.add_span_processor(processor)
|
||||
trace.set_tracer_provider(provider)
|
||||
|
||||
return trace.get_tracer("github-merge-queue-metrics")
|
||||
|
||||
|
||||
def export_pr_metrics_to_honeycomb(tracer, results, repo_owner, repo_name):
|
||||
"""Export PR merge queue metrics to Honeycomb as OTEL spans."""
|
||||
if tracer is None:
|
||||
return
|
||||
|
||||
print(f"\nExporting {len(results)} PR metrics to Honeycomb...")
|
||||
|
||||
for pull_number, started_at, merged_at, time_difference in results:
|
||||
# Create a span for each PR merge event
|
||||
# Use the actual timestamps from the PR for accurate timing
|
||||
span = tracer.start_span(
|
||||
"merge_queue_pr",
|
||||
start_time=int(started_at.timestamp() * 1e9), # Convert to nanoseconds
|
||||
)
|
||||
duration_seconds = time_difference.total_seconds()
|
||||
|
||||
# Set span attributes with PR details
|
||||
span.set_attribute("pr.number", pull_number)
|
||||
span.set_attribute("pr.repo_owner", repo_owner)
|
||||
span.set_attribute("pr.repo_name", repo_name)
|
||||
pr_url = f"https://github.com/{repo_owner}/{repo_name}/pull/{pull_number}"
|
||||
span.set_attribute("pr.url", pr_url)
|
||||
span.set_attribute("pr.merge_queue_started_at", started_at.isoformat())
|
||||
span.set_attribute("pr.merged_at", merged_at.isoformat())
|
||||
span.set_attribute("pr.merge_queue_duration_seconds", duration_seconds)
|
||||
span.set_attribute("pr.merge_queue_duration_minutes", duration_seconds / 60)
|
||||
|
||||
# Add day of week and time of day for analysis
|
||||
merged_at_est = merged_at.astimezone(EST)
|
||||
span.set_attribute("pr.merged_day_of_week", merged_at_est.strftime("%A"))
|
||||
span.set_attribute("pr.merged_hour", merged_at_est.hour)
|
||||
span.set_attribute("pr.is_weekend", merged_at_est.weekday() >= 5)
|
||||
|
||||
# End the span at the actual merge time
|
||||
span.end(end_time=int(merged_at.timestamp() * 1e9))
|
||||
|
||||
print("Export complete.")
|
||||
|
||||
|
||||
def shutdown_otel():
|
||||
"""Shutdown the OTEL tracer provider to flush pending spans."""
|
||||
if OTEL_AVAILABLE:
|
||||
provider = trace.get_tracer_provider()
|
||||
if hasattr(provider, "shutdown"):
|
||||
provider.shutdown()
|
||||
|
||||
|
||||
def load_cache():
|
||||
"""Load the cache from disk."""
|
||||
@@ -135,11 +227,30 @@ def main():
|
||||
action="store_true",
|
||||
help="Print a list of PRs that were removed from the merge queue (not merged)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--honeycomb-api-key",
|
||||
default=os.environ.get("HONEYCOMB_API_KEY"),
|
||||
help="Honeycomb API key for exporting metrics (default: HONEYCOMB_API_KEY env var)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--honeycomb-dataset",
|
||||
default=os.environ.get("HONEYCOMB_DATASET", "merge-queue-metrics"),
|
||||
help="Honeycomb dataset name (default: HONEYCOMB_DATASET env var or 'merge-queue-metrics')",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.token:
|
||||
parser.error("--token is required or set MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN env var")
|
||||
|
||||
# Set up OTEL tracer for Honeycomb export if API key is provided
|
||||
tracer = None
|
||||
if args.honeycomb_api_key:
|
||||
tracer = setup_otel_tracer(args.honeycomb_api_key, args.honeycomb_dataset)
|
||||
if tracer:
|
||||
print(f"Honeycomb export enabled (dataset: {args.honeycomb_dataset})")
|
||||
else:
|
||||
print("Honeycomb export disabled (no API key provided)")
|
||||
|
||||
repo_owner = args.owner
|
||||
repo_name = args.repo
|
||||
token = args.token
|
||||
@@ -307,6 +418,11 @@ def main():
|
||||
else:
|
||||
print("No PRs were removed from the merge queue in this range.")
|
||||
|
||||
# Export to Honeycomb if tracer is configured
|
||||
if tracer and results:
|
||||
export_pr_metrics_to_honeycomb(tracer, results, repo_owner, repo_name)
|
||||
shutdown_otel()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -2403,7 +2403,7 @@ tasks:
|
||||
- func: "bazel run"
|
||||
vars:
|
||||
target: //buildscripts:github_merge_queue_metrics -- --owner=10gen --repo=mms
|
||||
env: MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN=${MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN}
|
||||
env: MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN=${MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN} HONEYCOMB_API_KEY=${MERGE_QUEUE_ANALYTICS_HONEYCOMB_API_KEY} HONEYCOMB_DATASET=Zack-Merge-Queue
|
||||
|
||||
- name: merge_queue_metrics_mms
|
||||
tags: ["assigned_to_jira_team_devprod_correctness", "auxiliary"]
|
||||
@@ -2421,4 +2421,4 @@ tasks:
|
||||
- func: "bazel run"
|
||||
vars:
|
||||
target: //buildscripts:github_merge_queue_metrics -- --owner=10gen --repo=mms
|
||||
env: MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN=${MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN}
|
||||
env: MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN=${MERGE_QUEUE_ANALYTICS_GITHUB_TOKEN} HONEYCOMB_API_KEY=${MERGE_QUEUE_ANALYTICS_HONEYCOMB_API_KEY} HONEYCOMB_DATASET=Zack-Merge-Queue
|
||||
|
||||
24
poetry.lock
generated
24
poetry.lock
generated
@@ -2713,6 +2713,28 @@ opentelemetry-proto = "1.35.0"
|
||||
opentelemetry-sdk = ">=1.35.0,<1.36.0"
|
||||
typing-extensions = ">=4.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-exporter-otlp-proto-http"
|
||||
version = "1.35.0"
|
||||
description = "OpenTelemetry Collector Protobuf over HTTP Exporter"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["testing"]
|
||||
markers = "platform_machine != \"s390x\" and platform_machine != \"ppc64le\""
|
||||
files = [
|
||||
{file = "opentelemetry_exporter_otlp_proto_http-1.35.0-py3-none-any.whl", hash = "sha256:9a001e3df3c7f160fb31056a28ed7faa2de7df68877ae909516102ae36a54e1d"},
|
||||
{file = "opentelemetry_exporter_otlp_proto_http-1.35.0.tar.gz", hash = "sha256:cf940147f91b450ef5f66e9980d40eb187582eed399fa851f4a7a45bb880de79"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
googleapis-common-protos = ">=1.52,<2.0"
|
||||
opentelemetry-api = ">=1.15,<2.0"
|
||||
opentelemetry-exporter-otlp-proto-common = "1.35.0"
|
||||
opentelemetry-proto = "1.35.0"
|
||||
opentelemetry-sdk = ">=1.35.0,<1.36.0"
|
||||
requests = ">=2.7,<3.0"
|
||||
typing-extensions = ">=4.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "1.35.0"
|
||||
@@ -5896,4 +5918,4 @@ libdeps = ["cxxfilt", "eventlet", "flask", "flask-cors", "gevent", "lxml", "prog
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<4.0"
|
||||
content-hash = "33a897a6d5a0482f8d4d9378582c1f3a732d74fae6068e9b80a9ffb6cc7a3873"
|
||||
content-hash = "c1531f3ed6e9a5a5492221184f226041f98cfefc2b0928e5ad87e49a4d707e76"
|
||||
|
||||
@@ -187,6 +187,7 @@ opentelemetry-api = "*"
|
||||
opentelemetry-sdk = "*"
|
||||
opentelemetry-exporter-otlp-proto-common = "*"
|
||||
opentelemetry-exporter-otlp-proto-grpc = { version = "*", markers = "platform_machine != 's390x' and platform_machine != 'ppc64le'" }
|
||||
opentelemetry-exporter-otlp-proto-http = { version = "*", markers = "platform_machine != 's390x' and platform_machine != 'ppc64le'" }
|
||||
timeout-decorator = "0.5.0"
|
||||
|
||||
# This can be installed with "poetry install -E libdeps"
|
||||
|
||||
Reference in New Issue
Block a user