https://github.com/python/cpython/commit/6db952eae9591071eee1ecb279ac6aaa38052f52
commit: 6db952eae9591071eee1ecb279ac6aaa38052f52
branch: main
author: Mark Shannon <[email protected]>
committer: markshannon <[email protected]>
date: 2026-01-14T11:34:58Z
summary:

GH-143613: Add colours and some more edges to executor visualization graph 
(GH-143809)

files:
M Python/optimizer.c

diff --git a/Python/optimizer.c b/Python/optimizer.c
index 5ef4a5c4fa513c..d24e29b2b298e0 100644
--- a/Python/optimizer.c
+++ b/Python/optimizer.c
@@ -1917,6 +1917,24 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp)
     _Py_Executors_InvalidateAll(interp, 0);
 }
 
+static int
+escape_angles(const char *input, Py_ssize_t size, char *buffer) {
+    int written = 0;
+    for (Py_ssize_t i = 0; i < size; i++) {
+        char c = input[i];
+        if (c == '<' || c == '>') {
+            buffer[written++] = '&';
+            buffer[written++] = c == '>' ? 'g' : 'l';
+            buffer[written++] = 't';
+            buffer[written++] = ';';
+        }
+        else {
+            buffer[written++] = c;
+        }
+    }
+    return written;
+}
+
 static void
 write_str(PyObject *str, FILE *out)
 {
@@ -1928,7 +1946,17 @@ write_str(PyObject *str, FILE *out)
     }
     const char *encoded_str = PyBytes_AsString(encoded_obj);
     Py_ssize_t encoded_size = PyBytes_Size(encoded_obj);
-    fwrite(encoded_str, 1, encoded_size, out);
+    char buffer[120];
+    bool truncated = false;
+    if (encoded_size > 24) {
+        encoded_size = 24;
+        truncated = true;
+    }
+    int size = escape_angles(encoded_str, encoded_size, buffer);
+    fwrite(buffer, 1, size, out);
+    if (truncated) {
+        fwrite("...", 1, 3, out);
+    }
     Py_DECREF(encoded_obj);
 }
 
@@ -1950,6 +1978,85 @@ find_line_number(PyCodeObject *code, _PyExecutorObject 
*executor)
     return -1;
 }
 
+#define RED "#ff0000"
+#define WHITE "#ffffff"
+#define BLUE "#0000ff"
+#define BLACK "#000000"
+#define LOOP "#00c000"
+
+const char *COLORS[10] = {
+    "9",
+    "8",
+    "7",
+    "6",
+    "5",
+    "4",
+    "3",
+    "2",
+    "1",
+    WHITE,
+};
+
+#ifdef Py_STATS
+const char *
+get_background_color(_PyUOpInstruction const *inst, uint64_t max_hotness)
+{
+    uint64_t hotness = inst->execution_count;
+    int index = (hotness * 10)/max_hotness;
+    if (index > 9) {
+        index = 9;
+    }
+    if (index < 0) {
+        index = 0;
+    }
+    return COLORS[index];
+}
+
+const char *
+get_foreground_color(_PyUOpInstruction const *inst, uint64_t max_hotness)
+{
+    if(_PyUop_Uncached[inst->opcode] == _DEOPT) {
+        return RED;
+    }
+    uint64_t hotness = inst->execution_count;
+    int index = (hotness * 10)/max_hotness;
+    if (index > 3) {
+        return BLACK;
+    }
+    return WHITE;
+}
+#endif
+
+static void
+write_row_for_uop(_PyExecutorObject *executor, uint32_t i, FILE *out)
+{
+    /* Write row for uop.
+        * The `port` is a marker so that outgoing edges can
+        * be placed correctly. If a row is marked `port=17`,
+        * then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
+        * 
https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
+        */
+    _PyUOpInstruction const *inst = &executor->trace[i];
+    const char *opname = _PyOpcode_uop_name[inst->opcode];
+#ifdef Py_STATS
+    const char *bg_color = get_background_color(inst, 
executor->trace[0].execution_count);
+    const char *color = get_foreground_color(inst, 
executor->trace[0].execution_count);
+    fprintf(out, "        <tr><td port=\"i%d\" border=\"1\" color=\"%s\" 
bgcolor=\"%s\" ><font color=\"%s\"> %s &nbsp;--&nbsp; %" PRIu64 
"</font></td></tr>\n",
+        i, color, bg_color, color, opname, inst->execution_count);
+#else
+    const char *color = (_PyUop_Uncached[inst->opcode] == _DEOPT) ? RED : 
BLACK;
+    fprintf(out, "        <tr><td port=\"i%d\" border=\"1\" color=\"%s\" >%s 
op0=%" PRIu64 "</td></tr>\n", i, color, opname, inst->operand0);
+#endif
+}
+
+static bool
+is_stop(_PyUOpInstruction const *inst)
+{
+    uint16_t base_opcode = _PyUop_Uncached[inst->opcode];
+    return (base_opcode == _EXIT_TRACE || base_opcode == _DEOPT || base_opcode 
== _JUMP_TO_TOP);
+}
+
+
 /* Writes the node and outgoing edges for a single tracelet in graphviz format.
  * Each tracelet is presented as a table of the uops it contains.
  * If Py_STATS is enabled, execution counts are included.
@@ -1977,21 +2084,8 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
         fprintf(out, ": %d</td></tr>\n", line);
     }
     for (uint32_t i = 0; i < executor->code_size; i++) {
-        /* Write row for uop.
-         * The `port` is a marker so that outgoing edges can
-         * be placed correctly. If a row is marked `port=17`,
-         * then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
-         * 
https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
-         */
-        _PyUOpInstruction const *inst = &executor->trace[i];
-        uint16_t base_opcode = _PyUop_Uncached[inst->opcode];
-        const char *opname = _PyOpcode_uop_name[base_opcode];
-#ifdef Py_STATS
-        fprintf(out, "        <tr><td port=\"i%d\" border=\"1\" >%s -- %" 
PRIu64 "</td></tr>\n", i, opname, inst->execution_count);
-#else
-        fprintf(out, "        <tr><td port=\"i%d\" border=\"1\" >%s op0=%" 
PRIu64 "</td></tr>\n", i, opname, inst->operand0);
-#endif
-        if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP) {
+        write_row_for_uop(executor, i, out);
+        if (is_stop(&executor->trace[i])) {
             break;
         }
     }
@@ -2006,6 +2100,10 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
         uint16_t base_opcode = _PyUop_Uncached[inst->opcode];
         uint16_t flags = _PyUop_Flags[base_opcode];
         _PyExitData *exit = NULL;
+        if (base_opcode == _JUMP_TO_TOP) {
+            fprintf(out, "executor_%p:i%d -> executor_%p:i%d [color = \"" LOOP 
"\"]\n", executor, i, executor, inst->jump_target);
+            break;
+        }
         if (base_opcode == _EXIT_TRACE) {
             exit = (_PyExitData *)inst->operand0;
         }
@@ -2016,10 +2114,22 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
             assert(base_exit_opcode == _EXIT_TRACE || base_exit_opcode == 
_DYNAMIC_EXIT);
             exit = (_PyExitData *)exit_inst->operand0;
         }
-        if (exit != NULL && exit->executor != cold && exit->executor != 
cold_dynamic) {
-            fprintf(out, "executor_%p:i%d -> executor_%p:start\n", executor, 
i, exit->executor);
+        if (exit != NULL) {
+            if (exit->executor == cold || exit->executor == cold_dynamic) {
+#ifdef Py_STATS
+                /* Only mark as have cold exit if it has actually exited */
+                uint64_t diff = inst->execution_count - 
executor->trace[i+1].execution_count;
+                if (diff) {
+                    fprintf(out, "cold_%p%d [ label = \"%"  PRIu64  "\" shape 
= ellipse color=\"" BLUE "\" ]\n", executor, i, diff);
+                    fprintf(out, "executor_%p:i%d -> cold_%p%d\n", executor, 
i, executor, i);
+                }
+#endif
+            }
+            else {
+                fprintf(out, "executor_%p:i%d -> executor_%p:start\n", 
executor, i, exit->executor);
+            }
         }
-        if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP) {
+        if (is_stop(inst)) {
             break;
         }
     }
@@ -2031,6 +2141,7 @@ _PyDumpExecutors(FILE *out)
 {
     fprintf(out, "digraph ideal {\n\n");
     fprintf(out, "    rankdir = \"LR\"\n\n");
+    fprintf(out, "    node [colorscheme=greys9]\n");
     PyInterpreterState *interp = PyInterpreterState_Get();
     for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
         executor_to_gv(exec, out);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to