Commit 7d5513012b4e added stopwatch_start()/stopwatch_stop() calls
around every engine node recompute and change handler invocation.
With ~320 engine stopwatches, each iteration incurs hundreds of
heap allocations, mutex operations, and syscalls (clock_gettime
and write on a pipe fd).  This causes significant CPU increase in
scaled environments.

Guard the stopwatch_start()/stopwatch_stop() calls behind a new
per-node flag that defaults to false.  Add two appctl commands,
"inc-engine/enable-stopwatch" and "inc-engine/disable-stopwatch",
to toggle the flag at runtime.  Both commands accept an optional
node name argument for per-node granularity; without an argument
all nodes are affected.  The one-time stopwatch_create() calls
in engine_init() and engine_add_input_impl() are left unchanged
because they have zero per-iteration cost.

Fixes: 7d5513012b4e ("inc-proc-eng: Build stopwatches into every incremental 
node.")
Reported-at: https://redhat.atlassian.net/browse/FDP-4170
Assisted-by: Claude Opus 4.6, Claude Code
Signed-off-by: Dumitru Ceara <[email protected]>
---
 controller/ovn-controller.8.xml | 20 +++++++
 lib/inc-proc-eng.c              | 60 +++++++++++++++++++--
 lib/inc-proc-eng.h              |  3 ++
 northd/ovn-northd.8.xml         | 20 +++++++
 tests/ovn-northd.at             | 96 +++++++++++++++++++++++++++++++++
 5 files changed, 195 insertions(+), 4 deletions(-)

diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
index dc4d106e59..e355046901 100644
--- a/controller/ovn-controller.8.xml
+++ b/controller/ovn-controller.8.xml
@@ -896,6 +896,26 @@
       <dd>
         Reset <code>ovn-controller</code> engine counters.
       </dd>
+
+      <dt><code>inc-engine/enable-stopwatch</code> [<var>node</var>]</dt>
+      <dd>
+        Enables the per-node and per-handler stopwatches in the
+        incremental processing engine.  If <var>node</var> is specified,
+        only that node's stopwatches are enabled; otherwise all nodes
+        are affected.  While enabled, every engine node recompute and
+        change handler invocation is timed and the results can be
+        viewed with the <code>stopwatch/show</code> command.
+        Stopwatches are disabled by default because they add measurable
+        CPU overhead.
+      </dd>
+
+      <dt><code>inc-engine/disable-stopwatch</code> [<var>node</var>]</dt>
+      <dd>
+        Disables the per-node and per-handler stopwatches in the
+        incremental processing engine.  If <var>node</var> is specified,
+        only that node's stopwatches are disabled; otherwise all nodes
+        are affected.  This is the default state.
+      </dd>
       </dl>
     </p>
 
diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c
index a4b6c8cde3..bcb0848c3b 100644
--- a/lib/inc-proc-eng.c
+++ b/lib/inc-proc-eng.c
@@ -220,6 +220,46 @@ engine_list_stopwatch_cmd(struct unixctl_conn *conn, int 
argc OVS_UNUSED,
     ds_destroy(&output_str);
 }
 
+static void
+engine_set_stopwatch_cmd(struct unixctl_conn *conn, int argc,
+                         const char *argv[], bool enabled)
+{
+    const char *node_name = argc > 1 ? argv[1] : NULL;
+
+    struct engine_node *node;
+    bool found = false;
+    VECTOR_FOR_EACH (&engine_nodes, node) {
+        if (node_name && strcmp(node->name, node_name)) {
+            continue;
+        }
+        node->stopwatch_enabled = enabled;
+        found = true;
+        if (node_name) {
+            break;
+        }
+    }
+
+    if (node_name && !found) {
+        unixctl_command_reply_error(conn, "node not found");
+        return;
+    }
+    unixctl_command_reply(conn, NULL);
+}
+
+static void
+engine_enable_stopwatches_cmd(struct unixctl_conn *conn, int argc,
+                              const char *argv[], void *arg OVS_UNUSED)
+{
+    engine_set_stopwatch_cmd(conn, argc, argv, true);
+}
+
+static void
+engine_disable_stopwatches_cmd(struct unixctl_conn *conn, int argc,
+                               const char *argv[], void *arg OVS_UNUSED)
+{
+    engine_set_stopwatch_cmd(conn, argc, argv, false);
+}
+
 static void
 engine_get_compute_failure_info(struct engine_node *node)
 {
@@ -256,6 +296,10 @@ engine_init(struct engine_node *node, struct engine_arg 
*arg)
                              engine_set_log_timeout_cmd, NULL);
     unixctl_command_register("inc-engine/list-stopwatches", "", 0, 1,
                              engine_list_stopwatch_cmd, NULL);
+    unixctl_command_register("inc-engine/enable-stopwatch", "[node]", 0, 1,
+                             engine_enable_stopwatches_cmd, NULL);
+    unixctl_command_register("inc-engine/disable-stopwatch", "[node]", 0, 1,
+                             engine_disable_stopwatches_cmd, NULL);
 }
 
 void
@@ -455,9 +499,13 @@ static enum engine_node_state
 run_recompute_callback(struct engine_node *node)
 {
     enum engine_node_state ret;
-    stopwatch_start(node->name, time_msec());
+    if (node->stopwatch_enabled) {
+        stopwatch_start(node->name, time_msec());
+    }
     ret = node->run(node, node->data);
-    stopwatch_stop(node->name, time_msec());
+    if (node->stopwatch_enabled) {
+        stopwatch_stop(node->name, time_msec());
+    }
     return ret;
 }
 
@@ -465,9 +513,13 @@ static enum engine_input_handler_result
 run_change_handler(struct engine_node *node, struct engine_node_input *input)
 {
     enum engine_input_handler_result ret;
-    stopwatch_start(input->change_handler_name, time_msec());
+    if (node->stopwatch_enabled) {
+        stopwatch_start(input->change_handler_name, time_msec());
+    }
     ret = input->change_handler(node, node->data);
-    stopwatch_stop(input->change_handler_name, time_msec());
+    if (node->stopwatch_enabled) {
+        stopwatch_stop(input->change_handler_name, time_msec());
+    }
     return ret;
 }
 
diff --git a/lib/inc-proc-eng.h b/lib/inc-proc-eng.h
index 1cb2466b23..ece33e1beb 100644
--- a/lib/inc-proc-eng.h
+++ b/lib/inc-proc-eng.h
@@ -282,6 +282,9 @@ struct engine_node {
 
     /* Indication if the node writes to SB DB. */
     bool sb_write;
+
+    /* Whether stopwatches are enabled for this node. */
+    bool stopwatch_enabled;
 };
 
 /* Initialize the data for the engine nodes. It calls each node's
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index a8db49f659..c2670601a7 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -257,6 +257,26 @@
         node are listed.
       </dd>
 
+      <dt><code>inc-engine/enable-stopwatch</code> [<var>node</var>]</dt>
+      <dd>
+        Enables the per-node and per-handler stopwatches in the
+        incremental processing engine.  If <var>node</var> is specified,
+        only that node's stopwatches are enabled; otherwise all nodes
+        are affected.  While enabled, every engine node recompute and
+        change handler invocation is timed and the results can be
+        viewed with the <code>stopwatch/show</code> command.
+        Stopwatches are disabled by default because they add measurable
+        CPU overhead.
+      </dd>
+
+      <dt><code>inc-engine/disable-stopwatch</code> [<var>node</var>]</dt>
+      <dd>
+        Disables the per-node and per-handler stopwatches in the
+        incremental processing engine.  If <var>node</var> is specified,
+        only that node's stopwatches are disabled; otherwise all nodes
+        are affected.  This is the default state.
+      </dd>
+
       </dl>
     </p>
 
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 484642b7a6..c58f731e90 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -21760,3 +21760,99 @@ ct_next(ct_state=new|trk) {
 OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ovn-northd - engine stopwatches enable/disable])
+AT_KEYWORDS([ovn])
+ovn_start
+
+dnl By default, engine stopwatches are disabled.  Trigger a recompute to
+dnl ensure the engine runs, then verify that no samples were collected.
+check as northd ovn-appctl -t ovn-northd inc-engine/recompute
+check ovn-nbctl --wait=sb sync
+AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show northd], [0], [dnl
+Statistics for 'northd'
+  Total samples: 0
+  Maximum: 0 msec
+  Minimum: 0 msec
+  95th percentile: 0.000000 msec
+  Short term average: 0.000000 msec
+  Long term average: 0.000000 msec
+])
+
+dnl Enable stopwatches only for the "northd" node.
+check as northd ovn-appctl -t ovn-northd inc-engine/enable-stopwatch northd
+check as northd ovn-appctl -t ovn-northd inc-engine/recompute
+check ovn-nbctl --wait=sb sync
+
+dnl The "northd" stopwatch should have collected samples.
+OVS_WAIT_UNTIL([
+    stats=$(as northd ovn-appctl -t ovn-northd stopwatch/show northd)
+    echo "$stats" | grep -q 'Total samples: [[1-9]]'
+])
+
+dnl The "lr_nat" stopwatch should still have no samples.
+AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show lr_nat], [0], [dnl
+Statistics for 'lr_nat'
+  Total samples: 0
+  Maximum: 0 msec
+  Minimum: 0 msec
+  95th percentile: 0.000000 msec
+  Short term average: 0.000000 msec
+  Long term average: 0.000000 msec
+])
+
+dnl Enable stopwatches globally (no argument).
+check as northd ovn-appctl -t ovn-northd inc-engine/enable-stopwatch
+check as northd ovn-appctl -t ovn-northd stopwatch/reset
+check as northd ovn-appctl -t ovn-northd inc-engine/recompute
+check ovn-nbctl --wait=sb sync
+
+dnl Now "lr_nat" should also have collected samples.
+OVS_WAIT_UNTIL([
+    stats=$(as northd ovn-appctl -t ovn-northd stopwatch/show lr_nat)
+    echo "$stats" | grep -q 'Total samples: [[1-9]]'
+])
+
+dnl Disable stopwatches only for the "northd" node.
+check as northd ovn-appctl -t ovn-northd inc-engine/disable-stopwatch northd
+check as northd ovn-appctl -t ovn-northd stopwatch/reset
+check as northd ovn-appctl -t ovn-northd inc-engine/recompute
+check ovn-nbctl --wait=sb sync
+
+dnl Verify "northd" has no new samples but "lr_nat" still collects.
+AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show northd], [0], [dnl
+Statistics for 'northd'
+  Total samples: 0
+  Maximum: 0 msec
+  Minimum: 0 msec
+  95th percentile: 0.000000 msec
+  Short term average: 0.000000 msec
+  Long term average: 0.000000 msec
+])
+OVS_WAIT_UNTIL([
+    stats=$(as northd ovn-appctl -t ovn-northd stopwatch/show lr_nat)
+    echo "$stats" | grep -q 'Total samples: [[1-9]]'
+])
+
+dnl Disable stopwatches globally and verify no samples are collected.
+check as northd ovn-appctl -t ovn-northd inc-engine/disable-stopwatch
+check as northd ovn-appctl -t ovn-northd stopwatch/reset
+check as northd ovn-appctl -t ovn-northd inc-engine/recompute
+check ovn-nbctl --wait=sb sync
+AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show northd], [0], [dnl
+Statistics for 'northd'
+  Total samples: 0
+  Maximum: 0 msec
+  Minimum: 0 msec
+  95th percentile: 0.000000 msec
+  Short term average: 0.000000 msec
+  Long term average: 0.000000 msec
+])
+
+dnl Error case: non-existent node name.
+AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/enable-stopwatch 
nonexistent_node], [2], [], [ignore])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
-- 
2.54.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to