"""
Converts a SPARQL query JSON explanation file to a flamegraph .
Usage : python explanation_to_flamegraph . py explanation . json flamegraph . svg
"""
import json
import subprocess
from argparse import ArgumentParser
from pathlib import Path
from shutil import which
from tempfile import NamedTemporaryFile
parser = ArgumentParser (
prog = ' OxigraphFlamegraph ' ,
description = ' Builds a flamegraph from the Oxigraph query explanation JSON format ' ,
epilog = ' Text at the bottom of help ' )
parser . add_argument ( ' json_explanation ' , type = Path )
parser . add_argument ( ' flamegraph_svg ' , type = Path )
args = parser . parse_args ( )
def trace_line ( label : str , value : float ) :
return f " { label } { int ( value * 1_000_000 ) } "
with args . json_explanation . open ( ' rt ' ) as fp :
explanation = json . load ( fp )
trace = [ ]
if " parsing duration in seconds " in explanation :
trace . append ( trace_line ( " parsing " , explanation [ ' parsing duration in seconds ' ] ) )
if " planning duration in seconds " in explanation :
trace . append ( trace_line ( " planning " , explanation [ ' planning duration in seconds ' ] ) )
already_used_names = { }
def add_to_trace ( node , path ) :
path = f " { path } ; { node [ ' name ' ] . replace ( ' ' , ' ` ' ) } "
if path in already_used_names :
already_used_names [ path ] + = 1
path = f " { path } ` { already_used_names [ path ] } "
else :
already_used_names [ path ] = 0
samples = node [ ' duration in seconds ' ] - sum ( child [ ' duration in seconds ' ] for child in node . get ( " children " , ( ) ) )
if int ( samples * 1_000_000 ) > 0 :
trace . append ( trace_line ( path , samples ) )
for i , child in enumerate ( node . get ( " children " , ( ) ) ) :
add_to_trace ( child , path )
add_to_trace ( explanation [ " plan " ] , ' eval ' )
inferno = which ( ' inferno-flamegraph ' )
flamegraph_pl = which ( ' flamegraph.pl ' )
if inferno :
args . flamegraph_svg . write_text (
subprocess . run ( [ inferno ] , input = ' \n ' . join ( trace ) , stdout = subprocess . PIPE , text = True ) . stdout )
elif flamegraph_pl :
with NamedTemporaryFile ( ' w+t ' ) as fp :
fp . write ( ' \n ' . join ( trace ) )
fp . flush ( )
args . flamegraph_svg . write_text (
subprocess . run ( [ flamegraph_pl , fp . name ] , stdout = subprocess . PIPE , text = True ) . stdout )
else :
raise Exception (
' This script requires either the inferno-flamegraph from https://github.com/jonhoo/inferno either the flamegraph.pl script from https://github.com/brendangregg/FlameGraph to be installed and be in $PATH. ' )