Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/guides/add-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,19 @@ CI の build job では `scripts/build_tool_wrappers/` を `PATH` の先頭に

この actual build snapshot は、将来の build cache key や、同じ source から異なる binary が生じた場合の原因確認に使う前提の記録です。

### run placement / node status snapshot の方針

CI の共通 job は、benchmark 本体の `run_start` より前に `results/node_status_snapshot_run.json` を記録します。
app の `run.sh` からこの snapshot 用 helper を呼ぶ必要はありません。

この snapshot は scheduler-neutral な診断情報で、scheduler kind、scheduler が示す node list、観測できた host 数、CPU 数、memory、load average、GPU の軽量状態、run 前に見える GPU compute process の件数と memory 合計を記録します。
process ID、process name、user name は記録しません。
SLURM では可能なら allocation 内の各 node で軽い worker を動かします。
PBS / PJM / unknown scheduler では、取れる範囲の nodefile / environment / local host 情報だけを partial または unsupported として残します。

Result JSON には hash、summary、Measurement Artifact への参照だけを入れ、詳細な node list や GPU 状態は `results/node_status_snapshot_run.json` 側に残します。
Portal の public surface ではこの Measurement Artifact は表示しません。

### build cache の方針

cross build job と native `build_run` job の build phase では、共通 wrapper `scripts/build_with_cache.sh` が `build.sh` の前後で build artifact cache を扱います。
Expand Down
23 changes: 23 additions & 0 deletions result_server/routes/results_detail_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,17 @@ def _list_result_measurement_artifact_filenames(result, artifact_dir, *, include
seen.add(filename)
if os.path.isfile(os.path.join(artifact_dir, filename)):
filenames.append(filename)
for artifact_path in _iter_result_node_status_artifact_paths(result):
filename = stored_measurement_artifact_filename_from_path(
timestamp,
result_uuid,
artifact_path,
)
if not filename or filename in seen:
continue
seen.add(filename)
if os.path.isfile(os.path.join(artifact_dir, filename)):
filenames.append(filename)
return filenames


Expand Down Expand Up @@ -417,5 +428,17 @@ def _iter_result_timing_artifact_paths(result):
yield path


def _iter_result_node_status_artifact_paths(result):
node_status_snapshot = result.get("node_status_snapshot")
if not isinstance(node_status_snapshot, dict):
return
artifact = node_status_snapshot.get("artifact")
if not isinstance(artifact, dict) or artifact.get("type") != "file_reference":
return
path = _clean_result_value(artifact.get("path"))
if path:
yield path


def _clean_result_value(value):
return str(value or "").strip()
4 changes: 4 additions & 0 deletions result_server/templates/result_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
{{ render_titled_key_value_table("PA Data Summary", profile_rows, "meta-table") }}
{% endif %}

{% if node_status_rows %}
{{ render_titled_key_value_table("Node Status Snapshot", node_status_rows, "meta-table") }}
{% endif %}

{% if measurement_artifact_rows %}
<div class="section">
<h2>Measurement Artifacts</h2>
Expand Down
6 changes: 6 additions & 0 deletions result_server/tests/test_measurement_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ def test_builds_canonical_stored_names_for_legacy_profiles_and_timing_json():
UUID,
"results/qws_timing_CASE0.json",
) == f"measurement_artifact_{TIMESTAMP}_{UUID}_qws_timing_CASE0.json"
assert stored_measurement_artifact_filename_from_path(
TIMESTAMP,
UUID,
"results/node_status_snapshot_run.json",
) == f"measurement_artifact_{TIMESTAMP}_{UUID}_node_status_snapshot_run.json"


def test_profile_archive_candidates_keep_legacy_and_generic_names():
Expand All @@ -81,6 +86,7 @@ def test_profile_archive_candidates_keep_legacy_and_generic_names():
f"padata_{TIMESTAMP}_{UUID}.tgz",
f"padata_{TIMESTAMP}_{UUID}_padata_pairlist.tgz",
f"measurement_artifact_{TIMESTAMP}_{UUID}_qws_timing_CASE0.json",
f"measurement_artifact_{TIMESTAMP}_{UUID}_node_status_snapshot_run.json",
],
)
def test_recognizes_served_measurement_artifact_filenames(filename):
Expand Down
83 changes: 83 additions & 0 deletions result_server/tests/test_result_detail_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,31 @@ def app():
}
],
},
"node_status_snapshot": {
"schema_version": 1,
"kind": "node_status_snapshot",
"collection_status": "ok",
"collection_warnings": [],
"scheduler_kind": "slurm",
"summary": {
"scheduler_host_count": 2,
"observed_host_count": 2,
"cpu_logical_counts": [64],
"memory_total_mib_min": 262144,
"memory_total_mib_max": 262144,
"memory_available_mib_min": 131072,
"load_average_1m_max": 0.5,
"load_average_5m_max": 0.8,
"observed_gpu_count": 4,
"gpu_memory_used_total_mib": 0,
"gpu_compute_process_count": 0,
"gpu_compute_memory_used_mib": 0,
},
"artifact": {
"type": "file_reference",
"path": "results/node_status_snapshot_run.json",
},
},
}

FULL_QUALITY = {
Expand Down Expand Up @@ -204,6 +229,20 @@ def test_meta_info_section(self, app):
assert "build inputs hash matched" in html
assert "rccs-cloud" in html
assert "slurm" in html
assert "Node Status Snapshot" in html
assert "Hosts Observed" in html
assert "2/2" in html
assert "CPU Counts Observed" in html
assert "64" in html
assert "Host Memory Total" in html
assert "262144.000 MiB" in html
assert "Min Memory Available Before Run" in html
assert "131072.000 MiB" in html
assert "Max Load Average Before Run" in html
assert "1m=0.500; 5m=0.800" in html
assert "Compute Processes Before Run" in html
assert "0 process(es); 0.000 MiB" in html
assert "results/node_status_snapshot_run.json" in html
assert "Timing Observations" in html
assert "qws-case0-timers" in html
assert "producer=qws" in html
Expand Down Expand Up @@ -233,6 +272,8 @@ def test_public_surface_meta_omits_operator_fields(self, app):
assert "Cached Binary Created At" not in html
assert "Allocation Project ID" not in html
assert "Runner" not in html
assert "Node Status Snapshot" not in html
assert "node_status_snapshot_run" not in html
assert "Timing Observations" not in html
assert "qws_timing_CASE0" not in html
assert "rccs-cloud" not in html
Expand Down Expand Up @@ -398,6 +439,48 @@ def test_timing_observation_artifact_is_hidden_on_public_surface(self, app):
assert "Timing observation" not in html
assert filename not in html

def test_node_status_snapshot_artifact_is_linked_on_console_surface(self, app):
result = {
**FULL_RESULT,
"_server_uuid": "12345678-1234-1234-1234-123456789abc",
"_server_timestamp": "20260819_161329",
}
filename = (
"measurement_artifact_20260819_161329_"
"12345678-1234-1234-1234-123456789abc_node_status_snapshot_run.json"
)

with app.test_request_context():
html = _render_result_detail(result, FULL_QUALITY, [filename])

assert "Measurement Artifacts" in html
assert "Node status snapshot" in html
assert "Run placement" in html
assert "results/node_status_snapshot_run.json" in html
assert f'href="/results/{filename}"' in html

def test_node_status_snapshot_artifact_is_hidden_on_public_surface(self, app):
result = {
**FULL_RESULT,
"_server_uuid": "12345678-1234-1234-1234-123456789abc",
"_server_timestamp": "20260819_161329",
}
filename = (
"measurement_artifact_20260819_161329_"
"12345678-1234-1234-1234-123456789abc_node_status_snapshot_run.json"
)

with app.test_request_context():
html = _render_result_detail(
result,
FULL_QUALITY,
[filename],
public_surface=True,
)

assert "Node status snapshot" not in html
assert filename not in html

def test_vector_data_table(self, app):
with app.test_request_context():
html = _render_result_detail(FULL_RESULT, FULL_QUALITY)
Expand Down
Loading
Loading