Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions airflow-core/newsfragments/68521.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added the ``triggerer.batch_trigger_creation_duration`` metric, which measures the total time spent creating pending triggers during a single trigger runner cycle.
13 changes: 13 additions & 0 deletions airflow-core/src/airflow/jobs/triggerer_job_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,12 @@ def create_runtime_ti(

async def create_triggers(self):
"""Drain the to_create queue and create all new triggers that have been requested in the DB."""
# Emit batch creation duration only when triggers were processed.
has_work = bool(self.to_create)

if has_work:
creation_start = time.monotonic()

while self.to_create:
await asyncio.sleep(0)
context: Context | None = None
Expand Down Expand Up @@ -1419,6 +1425,13 @@ async def create_triggers(self):
"events": 0,
}

if has_work:
stats.timing(
"triggerer.batch_trigger_creation_duration",
(time.monotonic() - creation_start) * 1000,
tags=prune_dict({"team_name": self.team_name}),
)

async def cancel_triggers(self):
"""
Drain the to_cancel queue and ensure all triggers that are not in the DB are cancelled.
Expand Down
55 changes: 54 additions & 1 deletion airflow-core/tests/unit/jobs/test_triggerer_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -1588,12 +1588,65 @@ async def test_create_triggers_emits_queue_delay_metric(
):
await runner.create_triggers()

mock_timing.assert_called_once_with(
mock_timing.assert_any_call(
"triggerer.trigger_queue_delay",
1500,
tags=expected_tags,
)

@pytest.mark.asyncio
@pytest.mark.parametrize(
("team_name", "expected_tags"),
[
pytest.param("team_a", {"team_name": "team_a"}, id="with_team"),
pytest.param(None, {}, id="without_team"),
],
)
@patch("airflow.jobs.triggerer_job_runner.stats.timing")
@patch("airflow.jobs.triggerer_job_runner.Trigger._decrypt_kwargs")
@patch(
"airflow.jobs.triggerer_job_runner.TriggerRunner.get_trigger_by_classpath",
return_value=DateTimeTrigger,
)
async def test_create_triggers_emits_creation_duration_metric(
self,
mock_get_trigger_by_classpath,
mock_decrypt_kwargs,
mock_timing,
team_name,
expected_tags,
):
mock_decrypt_kwargs.return_value = {"moment": timezone.utcnow() + datetime.timedelta(hours=1)}

workload = workloads.RunTrigger.model_construct(
id=1,
classpath="abc",
encrypted_kwargs="fake",
)

runner = TriggerRunner()
runner.team_name = team_name
runner.to_create.append(workload)

await runner.create_triggers()

mock_timing.assert_called_once()

metric_name, metric_value = mock_timing.call_args.args

assert metric_name == "triggerer.batch_trigger_creation_duration"

# Specific metric_value is not being asserted here as time.monotonic is difficult
# to mock deterministically.
assert metric_value >= 0
assert mock_timing.call_args.kwargs == {"tags": expected_tags}

task = runner.triggers[workload.id]["task"]
task.cancel()
await asyncio.gather(task, return_exceptions=True)

await runner.cleanup_finished_triggers()

@pytest.mark.asyncio
@patch("airflow.sdk.execution_time.task_runner.SUPERVISOR_COMMS", create=True)
async def test_sync_state_to_supervisor(self, supervisor_builder):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,12 @@ metrics:
legacy_name: "dagrun.schedule_delay.{dag_id}"
name_variables: ["dag_id"]

- name: "triggerer.batch_trigger_creation_duration"
description: "Time in milliseconds spent creating a batch of pending triggers."
type: "timer"
legacy_name: "-"
name_variables: []

- name: "scheduler.critical_section_duration"
description: "Milliseconds spent in the critical section of scheduler loop"
type: "timer"
Expand Down