summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorSascha Roloff <sascha.roloff@huawei.com>2025-05-20 17:57:33 +0200
committerSascha Roloff <sascha.roloff@huawei.com>2025-05-20 19:07:56 +0200
commitfabb8f90506c1a09dd0cc3776f6a8eaa66036568 (patch)
tree28732c0f4b81daac84b38cf5bbb31265fed7844f /doc
parent52af0031631c562106dfc568b3483d3a5ced05e3 (diff)
downloadjustbuild-fabb8f90506c1a09dd0cc3776f6a8eaa66036568.tar.gz
invocation server: add critical-path computation
Diffstat (limited to 'doc')
-rwxr-xr-xdoc/invocations-http-server/server.py95
-rw-r--r--doc/invocations-http-server/templates/critical_path.html27
-rw-r--r--doc/invocations-http-server/templates/invocation.html3
3 files changed, 125 insertions, 0 deletions
diff --git a/doc/invocations-http-server/server.py b/doc/invocations-http-server/server.py
index f7091d58..2ea1bfda 100755
--- a/doc/invocations-http-server/server.py
+++ b/doc/invocations-http-server/server.py
@@ -24,6 +24,7 @@ import werkzeug.utils
from werkzeug.wrappers import Request, Response
from werkzeug.middleware.shared_data import SharedDataMiddleware
+from functools import cache
def core_config(conf):
new_conf = {}
@@ -52,6 +53,7 @@ class InvocationServer:
just_mr = None,
graph = "graph.json",
artifacts = "artifacts.json",
+ artifacts_to_build = "to-build.json",
profile = "profile.json",
meta = "meta.json"):
self.logsdir = logsdir
@@ -62,6 +64,7 @@ class InvocationServer:
self.profile = profile
self.graph = graph
self.artifacts = artifacts
+ self.artifacts_to_build = artifacts_to_build
self.meta = meta
self.templatepath = os.path.join(os.path.dirname(__file__), "templates")
self.jinjaenv = jinja2.Environment(
@@ -91,6 +94,9 @@ class InvocationServer:
rule("/invocations/<invocationidentifier:invocation>",
methods=("GET",),
endpoint="get_invocation"),
+ rule("/critical_path/<invocationidentifier:invocation>",
+ methods=("GET",),
+ endpoint="get_critical_path"),
], converters=dict(
invocationidentifier=InvocationIdentifierConverter,
hashidentifier=HashIdentifierConverter,
@@ -531,6 +537,7 @@ class InvocationServer:
action_count_cached += 1
params["action_count"] = action_count
params["action_count_cached"] = action_count_cached
+ params["fully_cached"] = (action_count == action_count_cached)
candidates.sort(reverse=True)
non_cached = []
params["more_noncached"] = None
@@ -545,6 +552,91 @@ class InvocationServer:
return self.render("invocation.html", params)
+ def do_get_critical_path(self, invocation):
+ params = {"invocation": invocation}
+
+ try:
+ with open(os.path.join(
+ self.logsdir, invocation, self.profile)) as f:
+ profile = json.load(f)
+ except:
+ profile = {}
+
+ try:
+ with open(os.path.join(
+ self.logsdir, invocation, self.graph)) as f:
+ graph = json.load(f)
+ except:
+ graph = {}
+
+ try:
+ with open(os.path.join(
+ self.logsdir, invocation, self.artifacts_to_build)) as f:
+ artifacts_to_build = json.load(f)
+ except:
+ artifacts_to_build = {}
+
+ @cache
+ def critical_path(artifact_type, source_id):
+ input_artifacts = []
+ source_duration = 0.0
+ if artifact_type == "ACTION":
+ input_artifacts = graph.get("actions", {}).get(source_id, {}).get("input", {}).values()
+ source_duration = profile.get("actions", {}).get(source_id, {}).get("duration", 0.0)
+ elif artifact_type == "TREE":
+ input_artifacts = graph.get("trees", {}).get(source_id, {}).values()
+ elif artifact_type == "TREE_OVERLAY":
+ input_artifacts = graph.get("tree_overlays", {}).get(source_id, {}).get("trees", [])
+ else:
+ return ([], 0.0)
+
+ max_path = []
+ max_duration = 0.0
+ for artifact_desc in input_artifacts:
+ path, duration = critical_path(
+ artifact_desc.get("type", {}),
+ artifact_desc.get("data", {}).get("id"))
+ if duration > max_duration:
+ max_duration = duration
+ max_path = path
+
+ path = max_path + [(artifact_type, source_id)]
+ duration = max_duration + source_duration
+ return (path, duration)
+
+ max_path = []
+ max_name = None
+ max_duration = 0.0
+ for artifact_name, artifact_desc in artifacts_to_build.items():
+ path, duration = critical_path(
+ artifact_desc.get("type", {}),
+ artifact_desc.get("data", {}).get("id"))
+ if duration > max_duration:
+ max_duration = duration
+ max_path = path
+ max_name = artifact_name
+
+ max_path_data = []
+ for artifact_type, source_id in reversed(max_path):
+ source_data = {}
+ if artifact_type == "ACTION":
+ action_profile = profile.get('actions', {}).get(source_id, {})
+ source_data = action_data(source_id, action_profile, graph)
+ source_data["name_prefix"] = "%5.1fs" % (action_profile.get("duration", 0.0),)
+ else:
+ source_data = {"name": source_id}
+ max_path_data.append({
+ "type": artifact_type,
+ "data": source_data
+ })
+
+ params["critical_artifact"] = {
+ "name": max_name,
+ "path": max_path_data,
+ "duration": '%0.3fs' % (max_duration,) if max_duration > 0 else None,
+ }
+ return self.render("critical_path.html", params)
+
def action_data(name, profile_value, graph):
data = { "name_prefix": "",
"name": name,
@@ -595,6 +687,8 @@ if __name__ == '__main__':
help="Name of the logged action-graph file")
parser.add_argument("--artifacts", dest="artifacts", default="artifacts.json",
help="Name of the logged artifacts file")
+ parser.add_argument("--artifacts-to-build", dest="artifacts_to_build", default="to-build.json",
+ help="Name of the logged artifacts-to-build file")
parser.add_argument("--profile", dest="profile", default="profile.json",
help="Name of the logged profile file")
parser.add_argument(
@@ -617,6 +711,7 @@ if __name__ == '__main__':
just_mr=just_mr,
graph=args.graph,
artifacts=args.artifacts,
+ artifacts_to_build=args.artifacts_to_build,
meta=args.meta,
profile=args.profile)
make_server(args.interface, args.port, app).serve_forever()
diff --git a/doc/invocations-http-server/templates/critical_path.html b/doc/invocations-http-server/templates/critical_path.html
new file mode 100644
index 00000000..8d818809
--- /dev/null
+++ b/doc/invocations-http-server/templates/critical_path.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+{% block heading %}
+Invocations {{invocation | e}}
+{% endblock %}
+
+{% from 'macros.html' import show_action %}
+
+{% block content %}
+<h1>Critical path for {{ invocation | e }}</h1>
+
+<h2>Overview</h2>
+<ul>
+ <li>Invocation: <a href="/invocations/{{ invocation | e }}"><tt>{{ invocation | e }}</tt></a></li>
+ <li>Critical artifact: <tt>{{ critical_artifact["name"] | e }}</tt></li>
+ <li>Duration: <tt>{{ critical_artifact["duration"] | e }}</tt></li>
+ <li>Path:</li>
+ <ul>
+ {% for entry in critical_artifact["path"] %}
+ {% if entry["type"] == "ACTION" %}
+ {{ show_action(entry["data"]) }}
+ {% else %}
+ <li>{{ entry["type"] | lower | e }} <tt>{{ entry["data"]["name"] | e }}</tt></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+</ul>
+{% endblock %}
diff --git a/doc/invocations-http-server/templates/invocation.html b/doc/invocations-http-server/templates/invocation.html
index c90d4272..6ccb0a56 100644
--- a/doc/invocations-http-server/templates/invocation.html
+++ b/doc/invocations-http-server/templates/invocation.html
@@ -78,6 +78,9 @@ Invocation {{invocation | e}}
{% if exit_code != None %}
<li> Exit code: <tt>{{ exit_code | e }}</tt></li>
{% endif %}
+ {% if not fully_cached %}
+ <li> <a href="/critical_path/{{invocation | e}}">Critical path</a></li>
+ {% endif %}
{% if artifacts %}
<li>
<details>