Fork of https://github.com/oxigraph/oxigraph.git for the purpose of NextGraph project
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
63 lines
2.3 KiB
63 lines
2.3 KiB
"""
|
|
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.')
|
|
|