Skip to content

Commit 6720924

Browse files
committed
Add colours and some more edges to executor visualization graph
1 parent fd6d41b commit 6720924

1 file changed

Lines changed: 130 additions & 19 deletions

File tree

Python/optimizer.c

Lines changed: 130 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,24 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp)
18881888
_Py_Executors_InvalidateAll(interp, 0);
18891889
}
18901890

1891+
static int
1892+
escape_angles(const char *input, Py_ssize_t size, char *buffer) {
1893+
int written = 0;
1894+
for (Py_ssize_t i = 0; i < size; i++) {
1895+
char c = input[i];
1896+
if (c == '<' || c == '>') {
1897+
buffer[written++] = '&';
1898+
buffer[written++] = c == '>' ? 'g' : 'l';
1899+
buffer[written++] = 't';
1900+
buffer[written++] = ';';
1901+
}
1902+
else {
1903+
buffer[written++] = c;
1904+
}
1905+
}
1906+
return written;
1907+
}
1908+
18911909
static void
18921910
write_str(PyObject *str, FILE *out)
18931911
{
@@ -1899,7 +1917,17 @@ write_str(PyObject *str, FILE *out)
18991917
}
19001918
const char *encoded_str = PyBytes_AsString(encoded_obj);
19011919
Py_ssize_t encoded_size = PyBytes_Size(encoded_obj);
1902-
fwrite(encoded_str, 1, encoded_size, out);
1920+
char buffer[120];
1921+
bool truncated = false;
1922+
if (encoded_size > 24) {
1923+
encoded_size = 24;
1924+
truncated = true;
1925+
}
1926+
int size = escape_angles(encoded_str, encoded_size, buffer);
1927+
fwrite(buffer, 1, size, out);
1928+
if (truncated) {
1929+
fwrite("...", 1, 3, out);
1930+
}
19031931
Py_DECREF(encoded_obj);
19041932
}
19051933

@@ -1921,6 +1949,85 @@ find_line_number(PyCodeObject *code, _PyExecutorObject *executor)
19211949
return -1;
19221950
}
19231951

1952+
#define RED "#ff0000"
1953+
#define WHITE "#ffffff"
1954+
#define BLUE "#0000ff"
1955+
#define BLACK "#000000"
1956+
#define LOOP "#00c000"
1957+
1958+
const char *COLORS[10] = {
1959+
"9",
1960+
"8",
1961+
"7",
1962+
"6",
1963+
"5",
1964+
"4",
1965+
"3",
1966+
"2",
1967+
"1",
1968+
WHITE,
1969+
};
1970+
1971+
#ifdef Py_STATS
1972+
const char *
1973+
get_background_color(_PyUOpInstruction const *inst, uint64_t max_hotness)
1974+
{
1975+
uint64_t hotness = inst->execution_count;
1976+
int index = (hotness * 10)/max_hotness;
1977+
if (index > 9) {
1978+
index = 9;
1979+
}
1980+
if (index < 0) {
1981+
index = 0;
1982+
}
1983+
return COLORS[index];
1984+
}
1985+
1986+
const char *
1987+
get_foreground_color(_PyUOpInstruction const *inst, uint64_t max_hotness)
1988+
{
1989+
if(_PyUop_Uncached[inst->opcode] == _DEOPT) {
1990+
return RED;
1991+
}
1992+
uint64_t hotness = inst->execution_count;
1993+
int index = (hotness * 10)/max_hotness;
1994+
if (index > 3) {
1995+
return BLACK;
1996+
}
1997+
return WHITE;
1998+
}
1999+
#endif
2000+
2001+
static void
2002+
write_row_for_uop(_PyExecutorObject *executor, uint32_t i, FILE *out)
2003+
{
2004+
/* Write row for uop.
2005+
* The `port` is a marker so that outgoing edges can
2006+
* be placed correctly. If a row is marked `port=17`,
2007+
* then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
2008+
* https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
2009+
*/
2010+
_PyUOpInstruction const *inst = &executor->trace[i];
2011+
const char *opname = _PyOpcode_uop_name[inst->opcode];
2012+
#ifdef Py_STATS
2013+
const char *bg_color = get_background_color(inst, executor->trace[0].execution_count);
2014+
const char *color = get_foreground_color(inst, executor->trace[0].execution_count);
2015+
fprintf(out, " <tr><td port=\"i%d\" border=\"1\" color=\"%s\" bgcolor=\"%s\" ><font color=\"%s\"> %s &nbsp;--&nbsp; %" PRIu64 "</font></td></tr>\n",
2016+
i, color, bg_color, color, opname, inst->execution_count);
2017+
#else
2018+
const char *color = (_PyUop_Uncached[inst->opcode] == _DEOPT) ? RED : BLACK;
2019+
fprintf(out, " <tr><td port=\"i%d\" border=\"1\" color=\"%s\" >%s op0=%" PRIu64 "</td></tr>\n", i, color, opname, inst->operand0);
2020+
#endif
2021+
}
2022+
2023+
static bool
2024+
is_stop(_PyUOpInstruction const *inst)
2025+
{
2026+
uint16_t base_opcode = _PyUop_Uncached[inst->opcode];
2027+
return (base_opcode == _EXIT_TRACE || base_opcode == _DEOPT || base_opcode == _JUMP_TO_TOP);
2028+
}
2029+
2030+
19242031
/* Writes the node and outgoing edges for a single tracelet in graphviz format.
19252032
* Each tracelet is presented as a table of the uops it contains.
19262033
* If Py_STATS is enabled, execution counts are included.
@@ -1948,21 +2055,8 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
19482055
fprintf(out, ": %d</td></tr>\n", line);
19492056
}
19502057
for (uint32_t i = 0; i < executor->code_size; i++) {
1951-
/* Write row for uop.
1952-
* The `port` is a marker so that outgoing edges can
1953-
* be placed correctly. If a row is marked `port=17`,
1954-
* then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
1955-
* https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
1956-
*/
1957-
_PyUOpInstruction const *inst = &executor->trace[i];
1958-
uint16_t base_opcode = _PyUop_Uncached[inst->opcode];
1959-
const char *opname = _PyOpcode_uop_name[base_opcode];
1960-
#ifdef Py_STATS
1961-
fprintf(out, " <tr><td port=\"i%d\" border=\"1\" >%s -- %" PRIu64 "</td></tr>\n", i, opname, inst->execution_count);
1962-
#else
1963-
fprintf(out, " <tr><td port=\"i%d\" border=\"1\" >%s op0=%" PRIu64 "</td></tr>\n", i, opname, inst->operand0);
1964-
#endif
1965-
if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP) {
2058+
write_row_for_uop(executor, i, out);
2059+
if (is_stop(&executor->trace[i])) {
19662060
break;
19672061
}
19682062
}
@@ -1977,6 +2071,10 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
19772071
uint16_t base_opcode = _PyUop_Uncached[inst->opcode];
19782072
uint16_t flags = _PyUop_Flags[base_opcode];
19792073
_PyExitData *exit = NULL;
2074+
if (base_opcode == _JUMP_TO_TOP) {
2075+
fprintf(out, "executor_%p:i%d -> executor_%p:i%d [color = \"" LOOP "\"]\n", executor, i, executor, inst->jump_target);
2076+
break;
2077+
}
19802078
if (base_opcode == _EXIT_TRACE) {
19812079
exit = (_PyExitData *)inst->operand0;
19822080
}
@@ -1987,10 +2085,22 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
19872085
assert(base_exit_opcode == _EXIT_TRACE || base_exit_opcode == _DYNAMIC_EXIT);
19882086
exit = (_PyExitData *)exit_inst->operand0;
19892087
}
1990-
if (exit != NULL && exit->executor != cold && exit->executor != cold_dynamic) {
1991-
fprintf(out, "executor_%p:i%d -> executor_%p:start\n", executor, i, exit->executor);
2088+
if (exit != NULL) {
2089+
if (exit->executor == cold || exit->executor == cold_dynamic) {
2090+
#ifdef Py_STATS
2091+
/* Only mark as have cold exit if it has actually exited */
2092+
uint64_t diff = inst->execution_count - executor->trace[i+1].execution_count;
2093+
if (diff) {
2094+
fprintf(out, "cold_%p%d [ label = \"%" PRIu64 "\" shape = ellipse color=\"" BLUE "\" ]\n", executor, i, diff);
2095+
fprintf(out, "executor_%p:i%d -> cold_%p%d\n", executor, i, executor, i);
2096+
}
2097+
#endif
2098+
}
2099+
else {
2100+
fprintf(out, "executor_%p:i%d -> executor_%p:start\n", executor, i, exit->executor);
2101+
}
19922102
}
1993-
if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP) {
2103+
if (is_stop(inst)) {
19942104
break;
19952105
}
19962106
}
@@ -2002,6 +2112,7 @@ _PyDumpExecutors(FILE *out)
20022112
{
20032113
fprintf(out, "digraph ideal {\n\n");
20042114
fprintf(out, " rankdir = \"LR\"\n\n");
2115+
fprintf(out, " node [colorscheme=greys9]\n");
20052116
PyInterpreterState *interp = PyInterpreterState_Get();
20062117
for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
20072118
executor_to_gv(exec, out);

0 commit comments

Comments
 (0)