Repository: apache/airflow

Issue:
Missing schedule_delay metric
<!--

Welcome to Apache Airflow!  For a smooth issue process, try to answer the following questions.
Don't worry if they're not all applicable; just try to include what you can :-)

If you need to include code snippets or logs, please put them in fenced code
blocks.  If they're super-long, please use the details tag like
<details><summary>super-long log</summary> lots of stuff </details>

Please delete these comment blocks before submitting the issue.

-->

<!--

IMPORTANT!!!

PLEASE CHECK "SIMILAR TO X EXISTING ISSUES" OPTION IF VISIBLE
NEXT TO "SUBMIT NEW ISSUE" BUTTON!!!

PLEASE CHECK IF THIS ISSUE HAS BEEN REPORTED PREVIOUSLY USING SEARCH!!!

Please complete the next sections or the issue will be closed.
These questions are the first thing we need to know to understand the context.

-->

**Apache Airflow version**: 2.0.0 but applicable to master

**Environment**: Running on ECS but not relevant to question


**What happened**: I am not seeing the metric dagrun.schedule_delay.<dag_id> being reported.  A search in the codebase seems to reveal that it no longer exists.   It was originally added in https://github.com/apache/airflow/pull/5050.

<!-- (please include exact error messages if you can) -->

<!-- What do you think went wrong? -->
I suspect either:
1.  This metric was intentionally removed, in which case the docs should be updated to remove it.
2. It was unintentionally removed during a refactor, in which case we should add it back.
3. I am bad at searching through code, and someone could hopefully point me to where it is reported from now. 

**How to reproduce it**:
https://github.com/apache/airflow/search?q=schedule_delay


<!--

How often does this problem occur? Once? Every time etc?

Any relevant logs to include? Put them here in side a detail tag:
<details><summary>x.log</summary> lots of stuff </details>

-->



Discussion:
Thanks for opening your first issue here! Be sure to follow the issue template!

@arbass22 Thanks for bringing this up! Looks like you're right, the functionality was removed when support for multiple schedulers (AIP-15) was introduced https://github.com/apache/airflow/commit/73b9163a8f55ce3d5bf6aec0a558952c27dd1b55#diff-bde85feb359b12bdd358aed4106ef4fccbd8fa9915e16b9abb7502912a1c1ab3L799.

@kaxil @ashb Do either of you know if this was intentional, or if it was removed accidentally during the merge?
Whops, no, not intentionally removed.
Found this because I was doing a deep dive through this recently because I am currently trying to figure out the best way to monitor for DAGs that never start. This is where this is getting emitted in the 1.10.X series, https://github.com/apache/airflow/blob/v1-10-stable/airflow/jobs/scheduler_job.py#L1296. If this is getting added back It would be really useful if the metric was emitted all the time instead of only when a new DagRun is getting created. This would result in more metrics getting sent but would allow for monitoring on if a DagRun was missed all together (for instance due to `catchup = False` being set on a Dag and that Dag running longer than the schedule period. Would definitely be willing to add a PR to add in this functionality. 
When you say "emitted all the time" what are you thinking? Could you provide a bit more detail to what you mean?
@ashb Ah yeah for sure!

So ideally everyone who created a DAG would know to setup `catchup=True` for DAGs where this is necessary. However this is not always the case in practice when you have many teams utilizing Airflow that you are trying to support. It would be nice to be able to use latency metrics to know when a DAG that should be starting isn't starting (this could be because a previous DAG run is still executing). However it also could be some other, unreported failure or error. If there was a steady stream of latency metrics for DAGs the absence of those metrics could be used to alert on. So instead of only emitting a latency metric every time a a new DagRun is created emitting a latency metric for each DAG every time the scheduler parses the DAGs. I guess in the case of a new DagRun not being created this would be the most recent latency.
Hello! Any updates on this? @ashb. This is definitely preventing us from upgrading to Airflow 2 as these latency metrics are really important to how we monitor Airflow. 

Resolving patch:
diff --git a/airflow/jobs/scheduler_job.py b/airflow/jobs/scheduler_job.py
--- a/airflow/jobs/scheduler_job.py
+++ b/airflow/jobs/scheduler_job.py
@@ -1616,7 +1616,7 @@ def _create_dag_runs(self, dag_models: Iterable[DagModel], session: Session) ->
             # create a new one. This is so that in the next Scheduling loop we try to create new runs
             # instead of falling in a loop of Integrity Error.
             if (dag.dag_id, dag_model.next_dagrun) not in active_dagruns:
-                dag.create_dagrun(
+                run = dag.create_dagrun(
                     run_type=DagRunType.SCHEDULED,
                     execution_date=dag_model.next_dagrun,
                     start_date=timezone.utcnow(),
@@ -1627,6 +1627,14 @@ def _create_dag_runs(self, dag_models: Iterable[DagModel], session: Session) ->
                     creating_job_id=self.id,
                 )
 
+                expected_start_date = dag.following_schedule(run.execution_date)
+                if expected_start_date:
+                    schedule_delay = run.start_date - expected_start_date
+                    Stats.timing(
+                        f'dagrun.schedule_delay.{dag.dag_id}',
+                        schedule_delay,
+                    )
+
         self._update_dag_next_dagruns(dag_models, session)
 
         # TODO[HA]: Should we do a session.flush() so we don't have to keep lots of state/object in


Test patch:
diff --git a/tests/jobs/test_scheduler_job.py b/tests/jobs/test_scheduler_job.py
--- a/tests/jobs/test_scheduler_job.py
+++ b/tests/jobs/test_scheduler_job.py
@@ -30,6 +30,7 @@
 
 import psutil
 import pytest
+from freezegun import freeze_time
 from parameterized import parameterized
 from sqlalchemy import func
 
@@ -3643,8 +3644,16 @@ def test_send_sla_callbacks_to_processor_sla_with_task_slas(self):
                 full_filepath=dag.fileloc, dag_id=dag_id
             )
 
-    def test_scheduler_sets_job_id_on_dag_run(self):
-        dag = DAG(dag_id='test_scheduler_sets_job_id_on_dag_run', start_date=DEFAULT_DATE)
+    @freeze_time(DEFAULT_DATE + datetime.timedelta(days=1, seconds=9))
+    @mock.patch('airflow.jobs.scheduler_job.Stats.timing')
+    def test_create_dag_runs(self, stats_timing):
+        """
+        Test various invariants of _create_dag_runs.
+
+        - That the run created has the creating_job_id set
+        - That we emit the right DagRun metrics
+        """
+        dag = DAG(dag_id='test_create_dag_runs', start_date=DEFAULT_DATE)
 
         DummyOperator(
             task_id='dummy',
@@ -3652,7 +3661,7 @@ def test_scheduler_sets_job_id_on_dag_run(self):
         )
 
         dagbag = DagBag(
-            dag_folder=os.path.join(settings.DAGS_FOLDER, "no_dags.py"),
+            dag_folder=os.devnull,
             include_examples=False,
             read_dags_from_db=True,
         )
@@ -3666,6 +3675,10 @@ def test_scheduler_sets_job_id_on_dag_run(self):
         with create_session() as session:
             self.scheduler_job._create_dag_runs([dag_model], session)
 
+        stats_timing.assert_called_once_with(
+            "dagrun.schedule_delay.test_create_dag_runs", datetime.timedelta(seconds=9)
+        )
+
         assert dag.get_last_dagrun().creating_job_id == self.scheduler_job.id
 
     def test_extra_operator_links_not_loaded_in_scheduler_loop(self):