From a62f8a0dd32026512febd05612da1a6b0c31af57 Mon Sep 17 00:00:00 2001 From: Jason Harper Date: Wed, 30 Oct 2024 11:12:13 -0700 Subject: [PATCH] PerfSpect 3 - new design, implementation, and features (#75) --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/1-support-bugs.yml | 30 - .github/ISSUE_TEMPLATE/2-feature-request.yml | 21 - .github/ISSUE_TEMPLATE/config.yml | 1 - .github/dependabot.yml | 42 + .github/mock_mlc | 58 + .github/workflows/build-test.yml | 67 + .github/workflows/build.yml | 36 - .github/workflows/codeql.yml | 21 +- .gitignore | 12 +- CODE_OF_CONDUCT.md | 260 +-- CONTRIBUTING.md | 6 +- Dockerfile | 26 + LICENSE | 2 +- Makefile | 164 +- NOTICE | 1 + README.md | 214 +- SECURITY.md | 14 + SUPPORT.md | 4 + THIRD_PARTY_PROGRAMS | 617 ++++++ _version.txt | 1 - build.Dockerfile | 20 + builder/build.Dockerfile | 22 + builder/build.sh | 35 + cmd/config/config.go | 815 +++++++ cmd/flame/flame.go | 146 ++ cmd/metrics/event_defs.go | 232 ++ cmd/metrics/event_frame.go | 356 ++++ cmd/metrics/metadata.go | 544 +++++ cmd/metrics/metric.go | 248 +++ cmd/metrics/metric_defs.go | 240 +++ cmd/metrics/metric_defs_test.go | 75 + cmd/metrics/metrics.go | 1218 +++++++++++ cmd/metrics/nmi_watchdog.go | 106 + cmd/metrics/perf_mux.go | 58 + cmd/metrics/print.go | 270 +++ cmd/metrics/process.go | 225 ++ cmd/metrics/resources/base.html | 678 ++++++ .../events/x86_64/AuthenticAMD/bergamo.txt | 445 ++++ .../events/x86_64/AuthenticAMD/genoa.txt | 445 ++++ .../events/x86_64/GenuineIntel}/bdx.txt | 6 +- .../events/x86_64/GenuineIntel/clx.txt | 6 +- .../events/x86_64/GenuineIntel/emr.txt | 193 ++ .../x86_64/GenuineIntel/emr_nofixedtma.txt | 7 +- .../events/x86_64/GenuineIntel/gnr.txt | 175 ++ .../events/x86_64/GenuineIntel}/icx.txt | 6 +- .../x86_64/GenuineIntel}/icx_nofixedtma.txt | 5 - .../events/x86_64/GenuineIntel/skx.txt | 235 ++ .../events/x86_64/GenuineIntel/spr.txt | 5 - .../x86_64/GenuineIntel/spr_nofixedtma.txt | 133 ++ .../events/x86_64/GenuineIntel}/srf.txt | 5 - .../metrics/x86_64/AuthenticAMD/bergamo.json | 299 +++ .../metrics/x86_64/AuthenticAMD/genoa.json | 299 +++ .../metrics/x86_64/GenuineIntel/bdx.json | 10 +- .../metrics/x86_64/GenuineIntel/clx.json | 18 +- .../metrics/x86_64/GenuineIntel/emr.json | 0 .../x86_64/GenuineIntel/emr_nofixedtma.json | 0 .../metrics/x86_64/GenuineIntel/gnr.json | 374 ++++ .../metrics/x86_64/GenuineIntel/icx.json | 0 .../x86_64/GenuineIntel/icx_nofixedtma.json | 0 .../metrics/x86_64/GenuineIntel/skx.json | 466 ++++ .../metrics/x86_64/GenuineIntel/spr.json | 399 ++++ .../x86_64/GenuineIntel/spr_nofixedtma.json | 349 +++ .../metrics/x86_64/GenuineIntel/srf.json | 0 cmd/metrics/summary.go | 366 ++++ cmd/report/report.go | 508 +++++ cmd/root.go | 431 ++++ cmd/telemetry/telemetry.go | 365 ++++ docs/daemonset.yml | 28 - events/gnr.txt | 96 - events/metric_gnr.json | 186 -- go.mod | 37 + go.sum | 51 + hotspot.py | 174 -- internal/common/common.go | 403 ++++ internal/common/targets.go | 268 +++ internal/cpudb/cpu_defs.go | 60 + internal/cpudb/cpu_test.go | 178 ++ internal/cpudb/cpudb.go | 198 ++ internal/progress/multispinner.go | 115 + internal/progress/multispinner_test.go | 43 + internal/report/accelerator_defs.go | 61 + internal/report/benchmarking_table_helpers.go | 189 ++ internal/report/dimm_table_helpers.go | 655 ++++++ internal/report/gpu_defs.go | 125 ++ internal/report/html.go | 1043 +++++++++ internal/report/html_flamegraph.go | 187 ++ internal/report/report.go | 550 +++++ internal/report/stacks_helpers.go | 165 ++ internal/report/table_defs.go | 1891 +++++++++++++++++ internal/report/table_helpers.go | 1743 +++++++++++++++ internal/script/script.go | 470 ++++ internal/script/script_defs.go | 980 +++++++++ internal/script/script_test.go | 153 ++ internal/target/target.go | 788 +++++++ internal/target/target_test.go | 19 + internal/util/util.go | 405 ++++ internal/util/util_test.go | 34 + main.go | 10 + perf-collect.py | 659 ------ perf-collect.spec | 48 - perf-postprocess.py | 1291 ----------- requirements.txt | 7 - scripts/check_events.py | 104 + scripts/copyright_go.sh | 26 + scripts/filterperfspectmetrics.py | 60 + scripts/perfmonevents2perfspect.py | 56 + scripts/perfmonmetrics2perfspect.py | 131 ++ scripts/print_licenses.sh | 49 + scripts/targets2yaml.py | 67 + security.md | 5 - similarity-analyzer/README.md | 56 - similarity-analyzer/Reference/CLX/500.csv | 42 - similarity-analyzer/Reference/CLX/502.csv | 42 - similarity-analyzer/Reference/CLX/505.csv | 42 - similarity-analyzer/Reference/CLX/520.csv | 42 - similarity-analyzer/Reference/CLX/523.csv | 42 - similarity-analyzer/Reference/CLX/525.csv | 42 - similarity-analyzer/Reference/CLX/531.csv | 42 - similarity-analyzer/Reference/CLX/541.csv | 42 - similarity-analyzer/Reference/CLX/548.csv | 42 - similarity-analyzer/Reference/CLX/557.csv | 42 - similarity-analyzer/Reference/ICX/500.csv | 70 - similarity-analyzer/Reference/ICX/502.csv | 70 - similarity-analyzer/Reference/ICX/505.csv | 70 - similarity-analyzer/Reference/ICX/520.csv | 70 - similarity-analyzer/Reference/ICX/523.csv | 70 - similarity-analyzer/Reference/ICX/525.csv | 70 - similarity-analyzer/Reference/ICX/531.csv | 70 - similarity-analyzer/Reference/ICX/541.csv | 70 - similarity-analyzer/Reference/ICX/548.csv | 70 - similarity-analyzer/Reference/ICX/557.csv | 70 - similarity-analyzer/SimilarityAnalysis.xlsx | Bin 100376 -> 0 bytes similarity-analyzer/_version.txt | 1 - similarity-analyzer/data_formatter/README.md | 13 - similarity-analyzer/data_formatter/main.py | 174 -- .../data_formatter/requirements.txt | 2 - similarity-analyzer/dopca.py | 402 ---- similarity-analyzer/requirements.txt | 7 - src/__init__.py | 8 - src/base.html | 830 -------- src/calibrate.c | 38 - src/common.py | 27 - src/perf_helpers.py | 438 ---- src/prepare_perf_events.py | 235 -- staticcheck.conf | 4 + targets.yaml | 29 + tools/Makefile | 229 ++ tools/build.Dockerfile | 39 + tools/tsc/go.mod | 3 + tools/tsc/tsc.go | 11 + tools/tsc/tsc_amd64.go | 22 + tools/tsc/tsc_amd64.s | 27 + tools/tsc/tsc_arm64.go | 16 + version.txt | 1 + 155 files changed, 23711 insertions(+), 6265 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 .github/ISSUE_TEMPLATE/1-support-bugs.yml delete mode 100644 .github/ISSUE_TEMPLATE/2-feature-request.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/dependabot.yml create mode 100755 .github/mock_mlc create mode 100644 .github/workflows/build-test.yml delete mode 100644 .github/workflows/build.yml create mode 100644 Dockerfile create mode 100644 NOTICE create mode 100644 SECURITY.md create mode 100644 SUPPORT.md create mode 100644 THIRD_PARTY_PROGRAMS delete mode 100644 _version.txt create mode 100644 build.Dockerfile create mode 100644 builder/build.Dockerfile create mode 100755 builder/build.sh create mode 100644 cmd/config/config.go create mode 100644 cmd/flame/flame.go create mode 100644 cmd/metrics/event_defs.go create mode 100644 cmd/metrics/event_frame.go create mode 100644 cmd/metrics/metadata.go create mode 100644 cmd/metrics/metric.go create mode 100644 cmd/metrics/metric_defs.go create mode 100644 cmd/metrics/metric_defs_test.go create mode 100644 cmd/metrics/metrics.go create mode 100644 cmd/metrics/nmi_watchdog.go create mode 100644 cmd/metrics/perf_mux.go create mode 100644 cmd/metrics/print.go create mode 100644 cmd/metrics/process.go create mode 100644 cmd/metrics/resources/base.html create mode 100644 cmd/metrics/resources/events/x86_64/AuthenticAMD/bergamo.txt create mode 100644 cmd/metrics/resources/events/x86_64/AuthenticAMD/genoa.txt rename {events => cmd/metrics/resources/events/x86_64/GenuineIntel}/bdx.txt (96%) rename events/clx_skx.txt => cmd/metrics/resources/events/x86_64/GenuineIntel/clx.txt (97%) create mode 100644 cmd/metrics/resources/events/x86_64/GenuineIntel/emr.txt rename events/spr_emr_nofixedtma.txt => cmd/metrics/resources/events/x86_64/GenuineIntel/emr_nofixedtma.txt (93%) create mode 100644 cmd/metrics/resources/events/x86_64/GenuineIntel/gnr.txt rename {events => cmd/metrics/resources/events/x86_64/GenuineIntel}/icx.txt (97%) rename {events => cmd/metrics/resources/events/x86_64/GenuineIntel}/icx_nofixedtma.txt (95%) create mode 100644 cmd/metrics/resources/events/x86_64/GenuineIntel/skx.txt rename events/spr_emr.txt => cmd/metrics/resources/events/x86_64/GenuineIntel/spr.txt (97%) create mode 100644 cmd/metrics/resources/events/x86_64/GenuineIntel/spr_nofixedtma.txt rename {events => cmd/metrics/resources/events/x86_64/GenuineIntel}/srf.txt (94%) create mode 100644 cmd/metrics/resources/metrics/x86_64/AuthenticAMD/bergamo.json create mode 100644 cmd/metrics/resources/metrics/x86_64/AuthenticAMD/genoa.json rename events/metric_bdx.json => cmd/metrics/resources/metrics/x86_64/GenuineIntel/bdx.json (97%) rename events/metric_skx_clx.json => cmd/metrics/resources/metrics/x86_64/GenuineIntel/clx.json (97%) rename events/metric_spr_emr.json => cmd/metrics/resources/metrics/x86_64/GenuineIntel/emr.json (100%) rename events/metric_spr_emr_nofixedtma.json => cmd/metrics/resources/metrics/x86_64/GenuineIntel/emr_nofixedtma.json (100%) create mode 100644 cmd/metrics/resources/metrics/x86_64/GenuineIntel/gnr.json rename events/metric_icx.json => cmd/metrics/resources/metrics/x86_64/GenuineIntel/icx.json (100%) rename events/metric_icx_nofixedtma.json => cmd/metrics/resources/metrics/x86_64/GenuineIntel/icx_nofixedtma.json (100%) create mode 100644 cmd/metrics/resources/metrics/x86_64/GenuineIntel/skx.json create mode 100644 cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr.json create mode 100644 cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr_nofixedtma.json rename events/metric_srf.json => cmd/metrics/resources/metrics/x86_64/GenuineIntel/srf.json (100%) create mode 100644 cmd/metrics/summary.go create mode 100644 cmd/report/report.go create mode 100644 cmd/root.go create mode 100644 cmd/telemetry/telemetry.go delete mode 100644 docs/daemonset.yml delete mode 100644 events/gnr.txt delete mode 100644 events/metric_gnr.json create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 hotspot.py create mode 100644 internal/common/common.go create mode 100644 internal/common/targets.go create mode 100644 internal/cpudb/cpu_defs.go create mode 100644 internal/cpudb/cpu_test.go create mode 100644 internal/cpudb/cpudb.go create mode 100644 internal/progress/multispinner.go create mode 100644 internal/progress/multispinner_test.go create mode 100644 internal/report/accelerator_defs.go create mode 100644 internal/report/benchmarking_table_helpers.go create mode 100644 internal/report/dimm_table_helpers.go create mode 100644 internal/report/gpu_defs.go create mode 100644 internal/report/html.go create mode 100644 internal/report/html_flamegraph.go create mode 100644 internal/report/report.go create mode 100644 internal/report/stacks_helpers.go create mode 100644 internal/report/table_defs.go create mode 100644 internal/report/table_helpers.go create mode 100644 internal/script/script.go create mode 100644 internal/script/script_defs.go create mode 100644 internal/script/script_test.go create mode 100644 internal/target/target.go create mode 100644 internal/target/target_test.go create mode 100644 internal/util/util.go create mode 100644 internal/util/util_test.go create mode 100644 main.go delete mode 100644 perf-collect.py delete mode 100644 perf-collect.spec delete mode 100644 perf-postprocess.py delete mode 100644 requirements.txt create mode 100755 scripts/check_events.py create mode 100755 scripts/copyright_go.sh create mode 100755 scripts/filterperfspectmetrics.py create mode 100755 scripts/perfmonevents2perfspect.py create mode 100755 scripts/perfmonmetrics2perfspect.py create mode 100755 scripts/print_licenses.sh create mode 100755 scripts/targets2yaml.py delete mode 100644 security.md delete mode 100644 similarity-analyzer/README.md delete mode 100644 similarity-analyzer/Reference/CLX/500.csv delete mode 100644 similarity-analyzer/Reference/CLX/502.csv delete mode 100644 similarity-analyzer/Reference/CLX/505.csv delete mode 100644 similarity-analyzer/Reference/CLX/520.csv delete mode 100644 similarity-analyzer/Reference/CLX/523.csv delete mode 100644 similarity-analyzer/Reference/CLX/525.csv delete mode 100644 similarity-analyzer/Reference/CLX/531.csv delete mode 100644 similarity-analyzer/Reference/CLX/541.csv delete mode 100644 similarity-analyzer/Reference/CLX/548.csv delete mode 100644 similarity-analyzer/Reference/CLX/557.csv delete mode 100644 similarity-analyzer/Reference/ICX/500.csv delete mode 100644 similarity-analyzer/Reference/ICX/502.csv delete mode 100644 similarity-analyzer/Reference/ICX/505.csv delete mode 100644 similarity-analyzer/Reference/ICX/520.csv delete mode 100644 similarity-analyzer/Reference/ICX/523.csv delete mode 100644 similarity-analyzer/Reference/ICX/525.csv delete mode 100644 similarity-analyzer/Reference/ICX/531.csv delete mode 100644 similarity-analyzer/Reference/ICX/541.csv delete mode 100644 similarity-analyzer/Reference/ICX/548.csv delete mode 100644 similarity-analyzer/Reference/ICX/557.csv delete mode 100644 similarity-analyzer/SimilarityAnalysis.xlsx delete mode 100644 similarity-analyzer/_version.txt delete mode 100644 similarity-analyzer/data_formatter/README.md delete mode 100644 similarity-analyzer/data_formatter/main.py delete mode 100644 similarity-analyzer/data_formatter/requirements.txt delete mode 100644 similarity-analyzer/dopca.py delete mode 100644 similarity-analyzer/requirements.txt delete mode 100644 src/__init__.py delete mode 100644 src/base.html delete mode 100644 src/calibrate.c delete mode 100644 src/common.py delete mode 100644 src/perf_helpers.py delete mode 100644 src/prepare_perf_events.py create mode 100644 staticcheck.conf create mode 100644 targets.yaml create mode 100644 tools/Makefile create mode 100644 tools/build.Dockerfile create mode 100644 tools/tsc/go.mod create mode 100644 tools/tsc/tsc.go create mode 100644 tools/tsc/tsc_amd64.go create mode 100644 tools/tsc/tsc_amd64.s create mode 100644 tools/tsc/tsc_arm64.go create mode 100644 version.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..093ad79 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @harp-intel \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/1-support-bugs.yml b/.github/ISSUE_TEMPLATE/1-support-bugs.yml deleted file mode 100644 index 0faf97d..0000000 --- a/.github/ISSUE_TEMPLATE/1-support-bugs.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: šŸ› Bug Report/Support -description: Ask a question or report an issue -labels: [bug] -body: - - type: markdown - attributes: - value: | - Thank you for submitting a bug report. It helps make PerfSpect better. - - Please try to include as much information as possible. - - type: textarea - attributes: - label: Verbose output from perf-collect - render: shell - description: Copy the output of `./perf-collect` with `-v` flag (it will automatically format as a code block) - - type: textarea - attributes: - label: Verbose output from perf-postprocess - render: shell - description: Copy the output of `./perf-postprocess` with `-v` flag (it will automatically format as a code block) - - type: textarea - attributes: - label: What steps can reproduce the bug? - description: Explain the bug, system setup, and provide a code snippet that can reproduce it. - validations: - required: true - - type: textarea - attributes: - label: Additional information - description: Is there anything else you think we should know? diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.yml b/.github/ISSUE_TEMPLATE/2-feature-request.yml deleted file mode 100644 index d14c127..0000000 --- a/.github/ISSUE_TEMPLATE/2-feature-request.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: šŸš€ Feature Request -description: Suggest an idea, feature, or enhancement -labels: [enhancement] -body: - - type: markdown - attributes: - value: | - Thank you for submitting an idea. It helps make PerfSpect better. - - type: textarea - attributes: - label: What is the problem this feature would solve? - validations: - required: true - - type: textarea - attributes: - label: What is the feature you are proposing to solve the problem? - validations: - required: true - - type: textarea - attributes: - label: What alternatives have you considered? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 8da9b00..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b429378 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + + - package-ecosystem: "gomod" + directory: "/internal/common" + schedule: + interval: "daily" + + - package-ecosystem: "gomod" + directory: "/internal/cpudb" + schedule: + interval: "daily" + + - package-ecosystem: "gomod" + directory: "/internal/report" + schedule: + interval: "daily" + + - package-ecosystem: "gomod" + directory: "/internal/script" + schedule: + interval: "daily" + + - package-ecosystem: "gomod" + directory: "/internal/target" + schedule: + interval: "daily" + + - package-ecosystem: "gomod" + directory: "/internal/util" + schedule: + interval: "daily" + diff --git a/.github/mock_mlc b/.github/mock_mlc new file mode 100755 index 0000000..f58a5e6 --- /dev/null +++ b/.github/mock_mlc @@ -0,0 +1,58 @@ +#! /bin/bash +# mock_mlc +# arguments: +# --loaded_latency +# --bandwidth_matrix + +if [ "$1" == "--loaded_latency" ]; then +cat < $(PACKAGE_EXTERNAL).md5 +# Build the distribution package +.PHONY: dist +dist: resources check perfspect + rm -rf dist/perfspect + mkdir -p dist/perfspect/tools/x86_64 + cp LICENSE dist/perfspect/ + cp THIRD_PARTY_PROGRAMS dist/perfspect/ + cp NOTICE dist/perfspect/ + cp targets.yaml dist/perfspect/ + cp perfspect dist/perfspect/ + cd dist && tar -czf perfspect_$(VERSION_NUMBER).tgz perfspect + cd dist && md5sum perfspect_$(VERSION_NUMBER).tgz > perfspect_$(VERSION_NUMBER).tgz.md5.txt + rm -rf dist/perfspect + echo '{"version": "$(VERSION_NUMBER)", "date": "$(COMMIT_DATE)", "time": "$(COMMIT_TIME)", "commit": "$(COMMIT_ID)" }' | jq '.' > dist/manifest.json +ifneq ("$(wildcard /prebuilt)","") # /prebuilt is a directory in the container + cp -r /prebuilt/oss_source* dist/ +endif +# Run package-level unit tests +.PHONY: test test: - cd dist && tar -xvf perfspect.tgz && cp -r $(BINARY_FINAL) ../test/. - cd test && pytest + go test -v ./... + +.PHONY: update-deps +update-deps: + @echo "Updating Go dependencies..." + go get -u ./... + go mod tidy -format_check: - black --check *.py src +# Check code formatting +.PHONY: check_format +check_format: + @echo "Running gofmt to check for code formatting issues..." + @test -z "$(shell gofmt -l -s ./)" || { echo "[WARN] Formatting issues detected. Resolve with 'make format'"; exit 1; } + @echo "gofmt detected no issues" +# Format code +.PHONY: format format: - black *.py src + @echo "Running gofmt to format code..." + gofmt -l -w -s ./ + +.PHONY: check_vet +check_vet: + @echo "Running go vet to check for suspicious constructs..." + @test -z "$(shell go vet ./...)" || { echo "[WARN] go vet detected issues"; exit 1; } + @echo "go vet detected no issues" -style_error_check: - # ignore long lines and conflicts with black, i.e., black wins - flake8 *.py src --ignore=E501,W503,E203 +.PHONY: check_static +check_static: + @echo "Running staticcheck to check for bugs..." + go install honnef.co/go/tools/cmd/staticcheck@latest + staticcheck ./... -pytype: *.py src/*.py - pytype ./*.py +.PHONY: check_license +check_license: + @echo "Checking license headers..." + @for f in `find . -type f ! -path './perfspect_202*' ! -path './tools/bin/*' ! -path './internal/script/resources/*' ! -path './scripts/.venv/*' ! -path './test/output/*' ! -path './debug_out/*' \( -name "*.go" -o -name "*.s" -o -name "*.html" -o -name "Makefile" -o -name "*.sh" -o -name "*.Dockerfile" -o -name "*.py" \)`; do \ + if ! grep -E 'Copyright \(C\) [0-9]{4}-[0-9]{4} Intel Corporation' "$$f" >/dev/null; then echo "Error: license not found: $$f"; fail=1; fi; \ + done; if [ -n "$$fail" ]; then exit 1; fi -check: format_check style_error_check pytype +.PHONY: check +check: check_format check_vet check_static check_license -dist: check dist/$(PACKAGE_EXTERNAL) +.PHONY: clean +clean: + @echo "Cleaning up..." + rm -f perfspect + sudo rm -rf dist + rm -rf internal/script/resources/x86_64/* + rm -rf perfspect_2024-* + rm -rf debug_out/* + rm -rf test/output diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..e4d4974 --- /dev/null +++ b/NOTICE @@ -0,0 +1 @@ +These contents may have been developed with support from one or more Intel-operated generative artificial intelligence solutions. \ No newline at end of file diff --git a/README.md b/README.md index a1f11b5..3c98a66 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,183 @@ -Notice: We plan to share a new implementation of PerfSpect in the coming months. The new implementation of PerfSpect will enhance its functionality by offering "live" metric generation. This means that the current post-processing step will be optional. Additionally, PerfSpect will incorporate features from other Intel tools, streamlining their acquisition and deployment for users. Stay tuned!
-
-
    -

    PerfSpect

    -
-
+# PerfSpect +Analyze and Optimize Linux Servers [![Build](https://github.com/intel/PerfSpect/actions/workflows/build.yml/badge.svg)](https://github.com/intel/PerfSpect/actions/workflows/build.yml)[![CodeQL](https://github.com/intel/PerfSpect/actions/workflows/codeql.yml/badge.svg)](https://github.com/intel/PerfSpect/actions/workflows/codeql.yml)[![License](https://img.shields.io/badge/License-BSD--3-blue)](https://github.com/intel/PerfSpect/blob/master/LICENSE) -[![Static Badge](https://img.shields.io/badge/Live_Demo-red?style=for-the-badge)](https://intel.github.io/PerfSpect/) - -[Quick Start](#quick-start-requires-perf-installed) | [Output](#output) | [Deploy in Kubernetes](#deploy-in-kubernetes) | [Requirements](#requirements) | [Build from source](#build-from-source) +[Getting PerfSpect](#getting-perfspect) | [Running PerfSpect](#running-perfspect) | [Building PerfSpect](#building-perfspect-from-source)
-PerfSpect is a system performance characterization tool built on top of linux perf. It contains two parts: +## What is PerfSpect +This command-line tool is designed to help you analyze and optimize Linux servers and the software running on them. Whether youā€™re a system administrator, a developer, or a performance engineer, this tool provides comprehensive insights and actionable recommendations to enhance performance and efficiency. -perf-collect: Collects hardware events at a 5 second output interval with practically zero overhead since PMU's run in counting mode. +## Getting PerfSpect +``` +$ wget -qO- https://github.com/intel/PerfSpect/releases/latest/download/perfspect.tgz | tar xvz +$ cd perfspect +``` +## Running PerfSpect +PerfSpect includes a suite of commands designed to analyze and optimize both system and software performance. -- Collection mode: - - `sudo ./perf-collect` _default system wide_ - - `sudo ./perf-collect --socket` - - `sudo ./perf-collect --cpu` - - `sudo ./perf-collect --pid ` - - `sudo ./perf-collect --cid` _by default, selects the 5 containers using the most CPU at start of perf-collect. To monitor specific containers provide up to 5 comma separated cids i.e. ,_ -- Duration: - - `sudo ./perf-collect` _default run until terminated_ - - `sudo ./perf-collect --timeout 10` _run for 10 seconds_ - - `sudo ./perf-collect --app "myapp.sh myparameter"` _runs for duration of another process_ +Run `perfspect -h` for top level help. Note the available commands and options. -perf-postprocess: Calculates high level metrics from hardware events +### Quick Start for Users of Intel PerfSpect 1.x +PerfSpect has undergone a re-design for the 3.x release. Prior releases of PerfSpect required two stages: -- `./perf-postprocess` +1) collect raw event data +2) process the raw event data into user-friendly metrics -## Quick start (requires perf installed) +The 3.x design combines these two steps into one...producing user-friendly metrics. Run `perfspect metrics` for the new experience. -``` -wget -qO- https://github.com/intel/PerfSpect/releases/latest/download/perfspect.tgz | tar xvz -cd perfspect -sudo ./perf-collect --timeout 10 -./perf-postprocess -``` +Also available with 3.x are "live metrics" where metrics are printed to stdout as they are collected. Try `perfspect metrics --live`. -## Running perf-collect as a non-root user -As seen in the examples above, `sudo` is the standard approach to running perf-collect with elevated privileges. If `sudo` is not possible and running as the root user is not possible, then a user may request the following changes be made to the system by an administrator: -- sysctl -w kernel.perf_event_paranoid=0 -- sysctl -w kernel.nmi_watchdog=0 -- write '125' to all perf_event_mux_interval_ms files found under /sys/devices/*. +### Quick Start for Users of Intel System Health Inspector (AKA "svr-info") 2.x +Svr-info functionality is now included in PerfSpect. The svr-info configuration report is generated by running `perfspect report`. Benchmarks can be executed by running `perfspect report --benchmark all`. Run `perfspect flame` to generate a software flamegraph. And, run `perfspect telemetry` to collect system telemetry. -`for i in $(find /sys/devices -name perf_event_mux_interval_ms); do echo 125 > $i; done` +### Commands +| Command | Description | +| ------- | ----------- | +| [`perfspect config`](#config-command) | Modify system configuration | +| [`perfspect flame`](#flame-command) | Generate flamegraphs | +| [`perfspect metrics`](#metrics-command) | Monitor core and uncore metrics | +| [`perfspect report`](#report-command) | Generate configuration report | +| [`perfspect telemetry`](#telemetry-command) | Collect system telemetry | -Recommend returning these settings to their prior values when analysis with PerfSpect is complete. +Each command has additional help text that can be viewed by running `perfspect -h`. -## Output +#### Config Command +The `config` command provides a method to view and change various system configuration parameters. Run `perfspect config -h` to view the parameters that can be modified. USE CAUTION when changing system parameters. It is possible to configure the system in a way that it will no longer operate. In some cases, a reboot will be required to return to the default settings. -perf-collect outputs: -1. `perfstat.csv`: raw event counts with system metadata +Example: +``` +$ ./perfspect config --cores 24 --llc 2.0 --uncoremaxfreq 1.8 +... +``` +#### Flame Command +Software flamegraphs are useful in diagnosing software performance bottlenecks. Run `perfspect flame -h` to capture a system-wide software flamegraph. +#### Metrics Command +The `metrics` command provides system performance characterization metrics. The metrics provided are dependent on the platform architecture. -perf-postprocess outputs: -1. `metric_out.sys.average.csv`: average metrics -2. `metric_out.sys.csv`: metric values at every 5 second interval -3. `metric_out.html`: html view of a few select metrics +Example: +``` +$ ./perfspect metrics --duration 30 +emr ā£Æ collection complete -![basic_stats](https://raw.githubusercontent.com/wiki/intel/PerfSpect/newhtml.gif) +Metric files: + /home/jharper5/dev/pt/perfspect_2024-10-10_10-58-36/emr_metrics.csv + /home/jharper5/dev/pt/perfspect_2024-10-10_10-58-36/emr_metrics_summary.csv + /home/jharper5/dev/pt/perfspect_2024-10-10_10-58-36/emr_metrics_summary.html +``` +The `metrics` command supports two modes -- default and "live". Default mode behaves as above -- metrics are collected and saved into files for review. The "live" mode prints the metrics in a selected format, e.g., CSV, JSON, to stdout where they can be viewed in the console and/or redirected into a file or observability pipeline. -## Deploy in Kubernetes +See `perfspect metrics -h` for the extensive set of options and examples. -Modify the template [deamonset.yml](docs/daemonset.yml) to deploy in kubernetes +#### Report Command +The `report` command generates system configuration reports in a variety of formats. By default, all categories of information are collected: +``` +$ ./perfspect report +soc-PF4W5A3V ā¢æ collection complete + +Report files: + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-45-40/soc-PF4W5A3V.html + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-45-40/soc-PF4W5A3V.xlsx + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-45-40/soc-PF4W5A3V.json + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-45-40/soc-PF4W5A3V.txt +``` +It's possible to collect a subset of information by providing command line options. Note that by specifying only the `txt` format, it is printed to stdout, as well as written to a report file. +``` +$ ./perfspect report --bios --os --format txt +BIOS +==== +Vendor: Intel Corporation +Version: EGSDCRB1.SYS.1752.P05.2401050248 +Release Date: 01/05/2024 + +Operating System +================ +OS: Ubuntu 23.10 +Kernel: 6.5.0-44-generic +Boot Parameters: BOOT_IMAGE=/boot/vmlinuz-6.5.0-44-generic root=UUID=e6d667af-f0b7-450b-b409-9fe2647aeb38 ro +Microcode: 0x21000230 + +Report files: + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-47-55/emr.txt +``` +See `perfspect report -h` for all options. -## Requirements +###### Memory Benchmark Requirements +Memory benchmarks executed through the PerfSpect report command require the IntelĀ® Memory Latency Checker application. It can be downloaded from here: [MLC](https://www.intel.com/content/www/us/en/download/736633/intel-memory-latency-checker-intel-mlc.html). Once downloaded, extract the Linux executable and place it in the perfspect/tools/x86_64 directory. -**perf** - PerfSpect uses the Linux perf tool to collect PMU counters +#### Telemetry Command +The `telemetry` command runs telemetry collectors on the specified target(s) and then generates reports of the results. By default, all telemetry types are collected. To select telemetry types, additional command line options are available (see `perfspect telemetry -h`). +``` +$ ./perfspect telemetry --duration 30 +soc-PF4W5A3V ā£¾ collection complete + +Report files: + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-55-13/soc-PF4W5A3V_telem.html + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-55-13/soc-PF4W5A3V_telem.xlsx + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-55-13/soc-PF4W5A3V_telem.json + /home/myuser/dev/perfspect/perfspect_2024-09-03_17-55-13/soc-PF4W5A3V_telem.txt +``` -Different events require different minimum kernels (PerfSpect will automatically collect only supported events) -1. Base (CPU util, CPI, Cache misses, etc.) - - 3.10 -2. Uncore (NUMA traffic, DRAM traffic, etc.) - - 4.9 -3. TMA (Micro-architecture boundness breakdown) - - ICX, SPR: 5.10 - - BDX, SKX, CLX: 3.10 +### Common Command Options -## Build from source +#### Local vs. Remote Targets +By default, PerfSpect targets the local host, i.e., the host where PerfSpect is running. Remote system(s) can also be targetted when the remote systems are reachable through SSH from the local host. -Requires recent python. On successful build, binaries will be created in `dist` folder +**Important:** Ensure the remote user has password-less sudo access (or root privileges) to fully utilize PerfSpect's capabilities. +To target a single remote system using a pre-configured private key: +``` +$ ./perfspect report --target 192.168.1.42 --user fred --key ~/.ssh/fredkey +... +``` +To target a single remote system using a password: +``` +$ ./perfspect report --target 192.168.1.42 --user fred +fred@192.168.1.42's password: ****** +... +``` +To target more than one remote system, a YAML file is used to provide the necessary connection parameters, e.g.: ``` -pip3 install -r requirements.txt -make +$ cat targets.yaml +# This YAML file contains a list of remote targets with their corresponding properties. +# Each target has the following properties: +# name: The name of the target (optional) +# host: The IP address or host name of the target (required) +# port: The port number used to connect to the target via SSH (optional) +# user: The user name used to connect to the target via SSH (optional) +# key: The path to the private key file used to connect to the target via SSH (optional) +# pwd: The password used to connect to the target via SSH (optional) +# +# Note: If key and pwd are both provided, the key will be used for authentication. +# +# Security Notes: +# It is recommended to use a private key for authentication instead of a password. +# Keep this file in a secure location and do not expose it to unauthorized users. +# +# Below are examples. Modify them to match your environment. +targets: + - name: ELAINES_TARGET + host: 192.168.1.1 + port: + user: elaine + key: /home/elaine/.ssh/id_rsa + pwd: + - name: JERRYS_TARGET + host: 192.168.1.2 + port: 2222 + user: jerry + key: + pwd: george + +$ ./perfspect report --benchmark speed,memory --targets targets.yaml +... ``` +## Building PerfSpect from Source +### 1st Build +`builder/build.sh` builds the dependencies and the app in Docker containers that provide the required build environments. Assumes you have Docker installed on your development system. -_Note: Most metrics and events come from [perfmon](https://github.com/intel/perfmon) and [TMA v4.5](https://www.intel.com/content/www/us/en/docs/vtune-profiler/cookbook/2023-1/top-down-microarchitecture-analysis-method.html)_ +### Subsequent Builds +`make` builds the app. Assumes the dependencies have been built previously and that you have Go installed on your development system. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..3d8cd19 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +Security Policy +=============== + +## Report a Vulnerability + +Submit your vulnerabilities as bug reports to the GitHub issues page. Refer to GitHub's [Creating an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue) for instructions. + +Provide the following information: +* Title +* Location of the vulnerability +* Steps to reproduce +* Expected result +* Actual result +* Proof \ No newline at end of file diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..5952dc1 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,4 @@ +Support +======= + +Submit your support needs to the GitHub issues page. Refer to GitHub's [Creating an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue) for instructions. \ No newline at end of file diff --git a/THIRD_PARTY_PROGRAMS b/THIRD_PARTY_PROGRAMS new file mode 100644 index 0000000..20c4198 --- /dev/null +++ b/THIRD_PARTY_PROGRAMS @@ -0,0 +1,617 @@ +Third Party Programs File +This file is the "third-party-programs.txt" file specified in the associated Intel end user license agreement for the Intel software you are licensing. +Third party programs and their corresponding required notices and/or license terms are listed below. +------------------------------------------------------------- +async-profiler + * Copyright 2017 Andrei Pangin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +------------------------------------------------------------- +avx-turbo +MIT License + +Copyright (c) 2018 travisdowns + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------- +cpuid +** Copyright 2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2017,2018, +** 2020 by Todd Allen. +------------------------------------------------------------- +dmidecode +Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. +------------------------------------------------------------- +ethtool +* Copyright (C) 1998 David S. Miller (davem@dm.cobaltmicro.com) + * Portions Copyright 2001 Sun Microsystems + * Kernel 2.4 update Copyright 2001 Jeff Garzik + * Wake-on-LAN,natsemi,misc support by Tim Hockin + * Portions Copyright 2002 Intel + * Portions Copyright (C) Sun Microsystems 2008 + * do_test support by Eli Kupermann + * ETHTOOL_PHYS_ID support by Chris Leech + * e1000 support by Scott Feldman + * e100 support by Wen Tao + * ixgb support by Nicholas Nunley + * amd8111e support by Reeja John + * long arguments by Andi Kleen. + * SMSC LAN911x support by Steve Glendinning + * Rx Network Flow Control configuration support + * Various features by Ben Hutchings ; + * Copyright 2009, 2010 Solarflare Communications + * MDI-X set support by Jesse Brandeburg + * Copyright 2012 Intel Corporation + * vmxnet3 support by Shrikrishna Khare + * Various features by Ben Hutchings ; + * Copyright 2008-2010, 2013-2016 Ben Hutchings + * QSFP+/QSFP28 DOM support by Vidya Sagar Ravipati +------------------------------------------------------------- +iostat +* iostat: report CPU and I/O statistics + * (C) 1998-2023 by Sebastien GODARD (sysstat orange.fr) + * + *************************************************************************** + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * + * for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * + *************************************************************************** +------------------------------------------------------------- +ipmitool +Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +Redistribution of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +Redistribution in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +Neither the name of Sun Microsystems, Inc. or the names of +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +This software is provided "AS IS," without a warranty of any kind. +ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, +INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. +SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE +FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING +OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL +SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, +OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR +PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF +LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +------------------------------------------------------------- +lshw +Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS +------------------------------------------------------------- +lspci + * Copyright (c) 1997--2020 Martin Mares + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later +------------------------------------------------------------- +mpstat + * (C) 2000-2023 by Sebastien GODARD (sysstat orange.fr) + * Copyright (C) 2022 Oracle and/or its affiliates. + * + *************************************************************************** + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * + * for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * + *************************************************************************** +------------------------------------------------------------- +pcm +BSD 3-Clause License + +Copyright (c) 2009-2024, Intel Corporation +Copyright (c) 2016-2020, opcm +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------- +perf +The Linux Kernel is provided under: + + SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + +Being under the terms of the GNU General Public License version 2 only, +according with: + + LICENSES/preferred/GPL-2.0 + +With an explicit syscall exception, as stated at: + + LICENSES/exceptions/Linux-syscall-note + +In addition, other licenses may also apply. Please see: + + Documentation/process/license-rules.rst + +for more details. + +All contributions to the Linux Kernel are subject to this COPYING file. +------------------------------------------------------------- +sadc + * (C) 1999-2023 by Sebastien GODARD (sysstat orange.fr) + * + *************************************************************************** + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * + * for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * + *************************************************************************** +------------------------------------------------------------- +sar + * (C) 1999-2023 by Sebastien GODARD (sysstat orange.fr) + * + *************************************************************************** + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * + * for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * + *************************************************************************** +------------------------------------------------------------- +spectre-meltdown-checker +Copyright not available/not identified +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. +The precise terms and conditions for copying, distribution and modification follow. +TERMS AND CONDITIONS +0. Definitions. +ā€œThis Licenseā€ refers to version 3 of the GNU General Public License. + +ā€œCopyrightā€ also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. +ā€œThe Programā€ refers to any copyrightable work licensed under this License. Each licensee is addressed as ā€œyouā€. ā€œLicenseesā€ and ā€œrecipientsā€ may be individuals or organizations. +To ā€œmodifyā€ a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a ā€œmodified versionā€ of the earlier work or a work ā€œbased onā€ the earlier work. +A ā€œcovered workā€ means either the unmodified Program or a work based on the Program. +To ā€œpropagateā€ a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. +To ā€œconveyā€ a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. +An interactive user interface displays ā€œAppropriate Legal Noticesā€ to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. +1. Source Code. +The ā€œsource codeā€ for a work means the preferred form of the work for making modifications to it. ā€œObject codeā€ means any non-source form of a work. +A ā€œStandard Interfaceā€ means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. +The ā€œSystem Librariesā€ of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A ā€œMajor Componentā€, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. +The ā€œCorresponding Sourceā€ for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. +The Corresponding Source for a work in source code form is that same work. +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: +a) The work must carry prominent notices stating that you modified it, and giving a relevant date. +b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to ā€œkeep intact all noticesā€. +c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. +d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an ā€œaggregateā€ if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: +a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. +b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. +c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. +d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. +e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. +A ā€œUser Productā€ is either (1) a ā€œconsumer productā€, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, ā€œnormally usedā€ refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. +ā€œInstallation Informationā€ for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. +7. Additional Terms. +ā€œAdditional permissionsā€ are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: +a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or +b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or +c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or +d) Limiting the use for publicity purposes of names of licensors or authors of the material; or +e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or +f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. +All other non-permissive additional terms are considered ā€œfurther restrictionsā€ within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. +An ā€œentity transactionā€ is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. +11. Patents. +A ā€œcontributorā€ is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's ā€œcontributor versionā€. +A contributor's ā€œessential patent claimsā€ are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, ā€œcontrolā€ includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. +In the following three paragraphs, a ā€œpatent licenseā€ is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To ā€œgrantā€ such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. ā€œKnowingly relyingā€ means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is ā€œdiscriminatoryā€ if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License ā€œor any later versionā€ applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM ā€œAS ISā€ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. +END OF TERMS AND CONDITIONS +------------------------------------------------------------- +sshpass +Copyright ā€“ not available/not listed +shpass was written by Shachar Shemesh for Lingnu Open Source Consulting Ltd. + +GNU General Public License v2.0 or later + +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS +------------------------------------------------------------- +stackcollapse-perf.pl +# Copyright 2012 Joyent, Inc. All rights reserved. +# Copyright 2012 Brendan Gregg. All rights reserved. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at docs/cddl1.txt or +# http://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at docs/cddl1.txt. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +------------------------------------------------------------- +stress-ng +Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +------------------------------------------------------------- +sysstat +License: GPLv2+ +Copyright: + * (C) 1998-2022 by Sebastien GODARD (sysstat orange.fr) + * + *************************************************************************** + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * + * for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * + *************************************************************************** +------------------------------------------------------------- +turbostat +Copyright (c) 2023 Intel Corporation. + * Len Brown +------------------------------------------------------------- + +Other names and brands may be claimed as the property of others. \ No newline at end of file diff --git a/_version.txt b/_version.txt deleted file mode 100644 index 3e1ad72..0000000 --- a/_version.txt +++ /dev/null @@ -1 +0,0 @@ -1.5.0 \ No newline at end of file diff --git a/build.Dockerfile b/build.Dockerfile new file mode 100644 index 0000000..898edf3 --- /dev/null +++ b/build.Dockerfile @@ -0,0 +1,20 @@ +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# image contains build environment for the application +# build the image (from repo root directory): +# $ docker image build -f build.Dockerfile --tag perfspect-builder:v1 . +# build the svr-info Go components using this image +# $ docker run --rm -v "$PWD":/workdir -w /workdir perfspect-builder:v1 make dist + +FROM golang:1.23 +WORKDIR /workdir +# pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change +COPY go.mod go.sum ./ +COPY internal/ ./internal/ +RUN go mod download && go mod verify +# Radamsa is used for fuzz testing +RUN curl -s https://gitlab.com/akihe/radamsa/uploads/a2228910d0d3c68d19c09cee3943d7e5/radamsa-0.6.c.gz | gzip -d | cc -O2 -x c -o /usr/local/bin/radamsa - +# jq is needed in the functional test to inspect the svr-info json reports +# zip is needed by CI/CD GHA +RUN apt update && apt install -y jq zip \ No newline at end of file diff --git a/builder/build.Dockerfile b/builder/build.Dockerfile new file mode 100644 index 0000000..fe6270c --- /dev/null +++ b/builder/build.Dockerfile @@ -0,0 +1,22 @@ +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# image contains svr-info release package build environment +# build image: +# $ docker build --build-arg TAG=v1 -f builder/build.Dockerfile --tag svr-info-builder:v1 . +# build svr-info: +# $ docker run --rm -v "$PWD":/localrepo -w /localrepo svr-info-builder:v1 make dist + +ARG REGISTRY= +ARG PREFIX= +ARG TAG= +# STAGE 1 - image contains pre-built tools components, rebuild the image to rebuild the tools components +FROM ${REGISTRY}${PREFIX}perfspect-tools:${TAG} AS tools + +# STAGE 2 - image contains svr-info's Go components build environment +FROM ${REGISTRY}${PREFIX}perfspect-builder:${TAG} AS perfspect +RUN mkdir /prebuilt +RUN mkdir /prebuilt/tools +COPY --from=tools /bin/ /prebuilt/tools +COPY --from=tools /oss_source* /prebuilt +RUN git config --global --add safe.directory /localrepo \ No newline at end of file diff --git a/builder/build.sh b/builder/build.sh new file mode 100755 index 0000000..c1a55d7 --- /dev/null +++ b/builder/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# run this script from repo's root directory + +set -ex + +TAG=v1 + +# build tools image +docker build -f tools/build.Dockerfile --tag perfspect-tools:$TAG ./tools +# Create a temporary container +id=$(docker create perfspect-tools:$TAG foo) + +# Copy the files from the container to your local disk +# Note: not used in build process, but useful to have around +docker cp "$id":/bin ./tools + +# Remove the temporary container +docker rm "$id" + +# build go app builder image +docker build -f build.Dockerfile --tag perfspect-builder:$TAG . + +# build perfspect release package builder image +docker build -f builder/build.Dockerfile --build-arg TAG=$TAG --tag perfspect-package-builder:$TAG . + +# build perfspect release package +docker container run \ + --volume "$(pwd)":/localrepo \ + -w /localrepo \ + --rm \ + perfspect-package-builder:$TAG \ + make dist diff --git a/cmd/config/config.go b/cmd/config/config.go new file mode 100644 index 0000000..fb9bf7b --- /dev/null +++ b/cmd/config/config.go @@ -0,0 +1,815 @@ +// Package config is a subcommand of the root command. It sets system configuration items on target platform(s). +package config + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "log/slog" + "math" + "os" + "perfspect/internal/common" + "perfspect/internal/progress" + "perfspect/internal/report" + "perfspect/internal/script" + "perfspect/internal/target" + "perfspect/internal/util" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + flagCores int + flagLlcSize float64 + flagAllCoreMaxFrequency float64 + flagUncoreMaxFrequency float64 + flagUncoreMinFrequency float64 + flagPower int + flagEpb int + flagEpp int + flagGovernor string + flagElc string +) + +const ( + flagCoresName = "cores" + flagLlcSizeName = "llc" + flagAllCoreMaxFrequencyName = "coremax" + flagUncoreMaxFrequencyName = "uncoremax" + flagUncoreMinFrequencyName = "uncoremin" + flagPowerName = "power" + flagEpbName = "epb" + flagEppName = "epp" + flagGovernorName = "governor" + flagElcName = "elc" +) + +// governorOptions - list of valid governor options +var governorOptions = []string{"performance", "powersave"} + +// elcOptions - list of valid elc options +var elcOptions = []string{"latency-optimized", "default"} + +const cmdName = "config" + +var examples = []string{ + fmt.Sprintf(" Set core count on local host: $ %s %s --cores 32", common.AppName, cmdName), + fmt.Sprintf(" Set multiple config items on local host: $ %s %s --coremaxfreq 3.0 --uncoremaxfreq 2.1 --power 120", common.AppName, cmdName), + fmt.Sprintf(" Set core count on remote target: $ %s %s --cores 32 --target 192.168.1.1 --user fred --key fred_key", common.AppName, cmdName), + fmt.Sprintf(" View current config on remote target: $ %s %s --target 192.168.1.1 --user fred --key fred_key", common.AppName, cmdName), + fmt.Sprintf(" Set governor on remote targets: $ %s %s --governor performance --targets targets.yaml", common.AppName, cmdName), +} + +var Cmd = &cobra.Command{ + Use: cmdName, + Short: "Modify target(s) system configuration", + Long: `Sets system configuration items on target platform(s). + +USE CAUTION! Target may become unstable. It is up to the user to ensure that the requested configuration is valid for the target. There is not an automated way to revert the configuration changes. If all else fails, reboot the target.`, + Example: strings.Join(examples, "\n"), + RunE: runCmd, + PreRunE: validateFlags, + GroupID: "primary", + Args: cobra.NoArgs, + SilenceErrors: true, +} + +func init() { + Cmd.Flags().IntVar(&flagCores, flagCoresName, 0, "") + Cmd.Flags().Float64Var(&flagLlcSize, flagLlcSizeName, 0, "") + Cmd.Flags().Float64Var(&flagAllCoreMaxFrequency, flagAllCoreMaxFrequencyName, 0, "") + Cmd.Flags().Float64Var(&flagUncoreMaxFrequency, flagUncoreMaxFrequencyName, 0, "") + Cmd.Flags().Float64Var(&flagUncoreMinFrequency, flagUncoreMinFrequencyName, 0, "") + Cmd.Flags().IntVar(&flagPower, flagPowerName, 0, "") + Cmd.Flags().IntVar(&flagEpb, flagEpbName, 0, "") + Cmd.Flags().IntVar(&flagEpp, flagEppName, 0, "") + Cmd.Flags().StringVar(&flagGovernor, flagGovernorName, "", "") + Cmd.Flags().StringVar(&flagElc, flagElcName, "", "") + + common.AddTargetFlags(Cmd) + + Cmd.SetUsageFunc(usageFunc) +} + +func usageFunc(cmd *cobra.Command) error { + cmd.Printf("Usage: %s [flags]\n\n", cmd.CommandPath()) + cmd.Printf("Examples:\n%s\n\n", cmd.Example) + cmd.Println("Flags:") + for _, group := range getFlagGroups() { + cmd.Printf(" %s:\n", group.GroupName) + for _, flag := range group.Flags { + cmd.Printf(" --%-20s %s\n", flag.Name, flag.Help) + } + } + cmd.Println("\nGlobal Flags:") + cmd.Parent().PersistentFlags().VisitAll(func(pf *pflag.Flag) { + flagDefault := "" + if cmd.Parent().PersistentFlags().Lookup(pf.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(pf.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", pf.Name, pf.Usage, flagDefault) + }) + return nil +} + +func getFlagGroups() []common.FlagGroup { + flags := []common.Flag{ + { + Name: flagCoresName, + Help: "set number of physical cores per processor", + }, + { + Name: flagLlcSizeName, + Help: "set LLC size (MB)", + }, + { + Name: flagAllCoreMaxFrequencyName, + Help: "set all-core max frequency (GHz)", + }, + { + Name: flagUncoreMaxFrequencyName, + Help: "set uncore max frequency (GHz)", + }, + { + Name: flagUncoreMinFrequencyName, + Help: "set uncore min frequency (GHz)", + }, + { + Name: flagPowerName, + Help: "set TDP per processor (W)", + }, + { + Name: flagEpbName, + Help: "set energy perf bias (EPB) from best performance (0) to most power savings (9)", + }, + { + Name: flagEppName, + Help: "set energy perf profile (EPP) from best performance (0) to most power savings (255)", + }, + { + Name: flagGovernorName, + Help: "set CPU scaling governor (" + strings.Join(governorOptions, ", ") + ")", + }, + { + Name: flagElcName, + Help: "set Efficiency Latency Control (SRF and GNR) (" + strings.Join(elcOptions, ", ") + ")", + }, + } + groups := []common.FlagGroup{} + groups = append(groups, common.FlagGroup{ + GroupName: "Configuration Options", + Flags: flags, + }) + groups = append(groups, common.GetTargetFlagGroup()) + return groups +} + +func validateFlags(cmd *cobra.Command, args []string) error { + if cmd.Flags().Lookup(flagCoresName).Changed && flagCores < 1 { + return fmt.Errorf("invalid core count: %d", flagCores) + } + if cmd.Flags().Lookup(flagLlcSizeName).Changed && flagLlcSize < 1 { + return fmt.Errorf("invalid LLC size: %.2f MB", flagLlcSize) + } + if cmd.Flags().Lookup(flagAllCoreMaxFrequencyName).Changed && flagAllCoreMaxFrequency < 0.1 { + return fmt.Errorf("invalid core frequency: %.1f GHz", flagAllCoreMaxFrequency) + } + if cmd.Flags().Lookup(flagUncoreMaxFrequencyName).Changed && flagUncoreMaxFrequency < 0.1 { + return fmt.Errorf("invalid uncore max frequency: %.1f GHz", flagUncoreMaxFrequency) + } + if cmd.Flags().Lookup(flagUncoreMinFrequencyName).Changed && flagUncoreMinFrequency < 0.1 { + return fmt.Errorf("invalid uncore min frequency: %.1f GHz", flagUncoreMinFrequency) + } + if cmd.Flags().Lookup(flagPowerName).Changed && flagPower < 1 { + return fmt.Errorf("invalid power: %d", flagPower) + } + if cmd.Flags().Lookup(flagEpbName).Changed && (flagEpb < 0 || flagEpb > 9) { + return fmt.Errorf("invalid epb: %d", flagEpb) + } + if cmd.Flags().Lookup(flagEppName).Changed && (flagEpp < 0 || flagEpp > 255) { + return fmt.Errorf("invalid epp: %d", flagEpp) + } + if cmd.Flags().Lookup(flagGovernorName).Changed && !util.StringInList(flagGovernor, governorOptions) { + return fmt.Errorf("invalid governor: %s", flagGovernor) + } + if cmd.Flags().Lookup(flagElcName).Changed && !util.StringInList(flagElc, elcOptions) { + return fmt.Errorf("invalid elc mode: %s", flagElc) + } + return nil +} + +func runCmd(cmd *cobra.Command, args []string) error { + // appContext is the application context that holds common data and resources. + appContext := cmd.Context().Value(common.AppContext{}).(common.AppContext) + localTempDir := appContext.TempDir + // get the targets + myTargets, err := common.GetTargets(cmd, true, true, localTempDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + if len(myTargets) == 0 { + err := fmt.Errorf("no targets specified") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + // create a temporary directory on each target + for _, myTarget := range myTargets { + targetTempRoot, _ := cmd.Flags().GetString(common.FlagTargetTempDirName) + targetTempDir, err := myTarget.CreateTempDirectory(targetTempRoot) + if err != nil { + err = fmt.Errorf("failed to create temporary directory: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + defer myTarget.RemoveDirectory(targetTempDir) + } + // print config prior to changes + if err := printConfig(myTargets, localTempDir); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + // were any changes requested? + changeRequested := false + flagGroups := getFlagGroups() + for _, flag := range flagGroups[0].Flags { + if cmd.Flags().Lookup(flag.Name).Changed { + changeRequested = true + break + } + } + if !changeRequested { + fmt.Println("No changes requested.") + return nil + } + // make requested changes, one target at a time + for _, myTarget := range myTargets { + if cmd.Flags().Lookup(flagCoresName).Changed { + out, err := setCoreCount(flagCores, myTarget, localTempDir) + if err != nil { + fmt.Printf("Error: %v, %s\n", err, out) + cmd.SilenceUsage = true + return err + } + } + if cmd.Flags().Lookup(flagLlcSizeName).Changed { + setLlcSize(flagLlcSize, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagAllCoreMaxFrequencyName).Changed { + setCoreFrequency(flagAllCoreMaxFrequency, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagUncoreMaxFrequencyName).Changed { + setUncoreFrequency(true, flagUncoreMaxFrequency, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagUncoreMinFrequencyName).Changed { + setUncoreFrequency(false, flagUncoreMinFrequency, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagPowerName).Changed { + setPower(flagPower, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagEpbName).Changed { + setEpb(flagEpb, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagEppName).Changed { + setEpp(flagEpp, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagGovernorName).Changed { + setGovernor(flagGovernor, myTarget, localTempDir) + } + if cmd.Flags().Lookup(flagElcName).Changed { + setElc(flagElc, myTarget, localTempDir) + } + } + // print config after making changes + fmt.Println("") // blank line + if err := printConfig(myTargets, localTempDir); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + return err + } + return nil +} + +func printConfig(myTargets []target.Target, localTempDir string) (err error) { + scriptNames := report.GetScriptNamesForTable(report.ConfigurationTableName) + var scriptsToRun []script.ScriptDefinition + for _, scriptName := range scriptNames { + scriptsToRun = append(scriptsToRun, script.GetScriptByName(scriptName)) + } + for _, myTarget := range myTargets { + multiSpinner := progress.NewMultiSpinner() + multiSpinner.AddSpinner(myTarget.GetName()) + multiSpinner.Start() + multiSpinner.Status(myTarget.GetName(), "collecting data") + // run the scripts + var scriptOutputs map[string]script.ScriptOutput + if scriptOutputs, err = script.RunScripts(myTarget, scriptsToRun, true, localTempDir); err != nil { + err = fmt.Errorf("failed to run collection scripts: %v", err) + multiSpinner.Status(myTarget.GetName(), "error collecting data") + multiSpinner.Finish() + return + } + multiSpinner.Status(myTarget.GetName(), "collection complete") + multiSpinner.Finish() + // process the tables, i.e., get field values from raw script output + tableNames := []string{report.ConfigurationTableName} + var tableValues []report.TableValues + if tableValues, err = report.Process(tableNames, scriptOutputs); err != nil { + err = fmt.Errorf("failed to process collected data: %v", err) + return + } + // create the report for this single table + var reportBytes []byte + if reportBytes, err = report.Create("txt", tableValues, scriptOutputs, myTarget.GetName()); err != nil { + err = fmt.Errorf("failed to create report: %v", err) + return + } + // print the report + fmt.Print(string(reportBytes)) + } + return +} + +func setCoreCount(cores int, myTarget target.Target, localTempDir string) (string, error) { + fmt.Printf("set core count per processor to %d on %s\n", cores, myTarget.GetName()) + setScript := script.ScriptDefinition{ + Name: "set core count", + Script: fmt.Sprintf(` +desired_core_count_per_socket=%d +num_cpus=$(ls /sys/devices/system/cpu/ | grep -E "^cpu[0-9]+$" | wc -l) +num_threads=$(lscpu | grep 'Thread(s) per core' | awk '{print $NF}') +num_sockets=$(lscpu | grep 'Socket(s)' | awk '{print $NF}') +num_cores_per_socket=$((num_cpus / num_sockets / num_threads)) + +# if desired core count is greater than current core count, exit +if [[ $desired_core_count_per_socket -gt $num_cores_per_socket ]]; then + echo "requested core count ($desired_core_count_per_socket) is greater than physical cores ($num_cores_per_socket)" + exit 1 +fi + +# enable all logical CPUs +echo 1 | tee /sys/devices/system/cpu/cpu*/online > /dev/null + +# if no cores to disable, exit +num_cores_to_disable_per_socket=$((num_cores_per_socket - desired_core_count_per_socket)) +if [[ $num_cores_to_disable_per_socket -eq 0 ]]; then + echo "no cpus to off-line" + exit 0 +fi + +# get lines from cpuinfo that match the fields we need +proc_cpuinfo_filtered=$(grep -E '(processor|core id|physical id)' /proc/cpuinfo) + +# loop through each line of text in proc_cpuinfo_filtered, creating a new record for each logical CPU +while IFS= read -r line; do + # if line contains 'processor', start a new record + if [[ $line =~ "processor" ]]; then + # if record isn't empty (is empty first time through loop), put the record in the list of cpuinfo records + if [[ -n "$record" ]]; then + cpuinfo+=("$record") + fi + record="$line"$'\n' + else + record+="$line"$'\n' + fi +done <<< "$proc_cpuinfo_filtered" +# add the last record +if [[ -n "$record" ]]; then + cpuinfo+=("$record") +fi + +# build a unique list of core ids from the records +core_ids=() +for record in "${cpuinfo[@]}"; do + core_id=$(echo "$record" | grep 'core id' | awk '{print $NF}') + found=0 + for id in "${core_ids[@]}"; do + if [[ "$id" == "$core_id" ]]; then + found=1 + break + fi + done + if [[ $found -eq 0 ]]; then + core_ids+=("$core_id") + fi +done + +# disable logical CPUs to reach the desired core count per socket +for ((socket=0; socket=0; i--)); do + core=${core_ids[i]} + if [[ $offlined_cores -eq $num_cores_to_disable_per_socket ]]; then + break + fi + offlined_cores=$((offlined_cores+1)) + # find record that matches socket and core and off-line the logical CPU + for record in "${cpuinfo[@]}"; do + processor=$(echo "$record" | grep 'processor' | awk '{print $NF}') + core_id=$(echo "$record" | grep 'core id' | awk '{print $NF}') + physical_id=$(echo "$record" | grep 'physical id' | awk '{print $NF}') + if [[ $physical_id -eq $socket && $core_id -eq $core ]]; then + echo "Off-lining processor $processor (socket $physical_id, core $core_id)" + echo 0 | tee /sys/devices/system/cpu/cpu"$processor"/online > /dev/null + num_disabled_cores=$((num_disabled_cores+1)) + fi + done + done +done +`, cores), + Superuser: true, + } + return runScript(myTarget, setScript, localTempDir) +} + +func setLlcSize(llcSize float64, myTarget target.Target, localTempDir string) { + fmt.Printf("set LLC size to %.2f MB on %s\n", llcSize, myTarget.GetName()) + scripts := []script.ScriptDefinition{} + scripts = append(scripts, script.GetScriptByName(script.LscpuScriptName)) + scripts = append(scripts, script.GetScriptByName(script.LspciBitsScriptName)) + scripts = append(scripts, script.GetScriptByName(script.LspciDevicesScriptName)) + scripts = append(scripts, script.GetScriptByName(script.L3WaySizeName)) + + outputs, err := script.RunScripts(myTarget, scripts, true, localTempDir) + if err != nil { + fmt.Fprintln(os.Stderr, err) + slog.Error("failed to run scripts on target", slog.String("target", myTarget.GetName()), slog.String("error", err.Error())) + return + } + maximumLlcSize, err := report.GetL3LscpuMB(outputs) + if err != nil { + fmt.Fprintln(os.Stderr, err) + slog.Error("failed to get maximum LLC size", slog.String("error", err.Error())) + return + } + // microarchitecture + uarch := report.UarchFromOutput(outputs) + cacheWays := report.GetCacheWays(uarch) + if len(cacheWays) == 0 { + fmt.Fprintln(os.Stderr, "failed to get cache ways") + slog.Error("failed to get cache ways") + return + } + // current LLC size + currentLlcSize, err := report.GetL3MSRMB(outputs) + if err != nil { + fmt.Fprintln(os.Stderr, err) + slog.Error("failed to get LLC size", slog.String("error", err.Error())) + return + } + if currentLlcSize == llcSize { + fmt.Printf("LLC size is already set to %.2f MB\n", llcSize) + return + } + // calculate the number of ways to set + cachePerWay := maximumLlcSize / float64(len(cacheWays)) + waysToSet := int(math.Ceil((llcSize / cachePerWay)) - 1) + if waysToSet >= len(cacheWays) { + fmt.Fprintf(os.Stderr, "LLC size is too large, maximum is %.2f MB\n", maximumLlcSize) + slog.Error("LLC size is too large", slog.Float64("llc size", llcSize), slog.Float64("current llc size", currentLlcSize)) + return + } + // set the LLC size + setScript := script.ScriptDefinition{ + Name: "set LLC size", + Script: fmt.Sprintf("wrmsr -a 0xC90 %d", cacheWays[waysToSet]), + Superuser: true, + Architectures: []string{"x86_64"}, + Families: []string{"6"}, // Intel only + Depends: []string{"wrmsr"}, + Lkms: []string{"msr"}, + } + _, err = runScript(myTarget, setScript, localTempDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: failed to set LLC size: %v\n", err) + } +} + +func setCoreFrequency(coreFrequency float64, myTarget target.Target, localTempDir string) { + fmt.Printf("set core frequency to %.1f GHz on %s\n", coreFrequency, myTarget.GetName()) + freqInt := uint64(coreFrequency * 10) + var msr uint64 + for i := 0; i < 8; i++ { + msr = msr | freqInt< 0 { + groups = append(groups, group) + } else { + slog.Warn("No collectable events in group", slog.String("ending", line)) + } + group = GroupDefinition{} // clear the list + } + } + if err = scanner.Err(); err != nil { + return + } + uncollectableEvents = uncollectable.ToSlice() + // expand uncore groups for all uncore devices + groups, err = expandUncoreGroups(groups, metadata) + + if uncollectable.Cardinality() != 0 { + slog.Warn("Events not collectable on target", slog.String("events", uncollectable.String())) + } + return +} + +// isCollectableEvent confirms if given event can be collected on the platform +func isCollectableEvent(event EventDefinition, metadata Metadata) bool { + // fixed-counter TMA + if !metadata.SupportsFixedTMA && (event.Name == "TOPDOWN.SLOTS" || strings.HasPrefix(event.Name, "PERF_METRICS.")) { + slog.Debug("Fixed counter TMA not supported on target", slog.String("event", event.Name)) + return false + } + // short-circuit for cpu events + if event.Device == "cpu" && !strings.HasPrefix(event.Name, "OCR") { + return true + } + // short-circuit off-core response events + if event.Device == "cpu" && + strings.HasPrefix(event.Name, "OCR") && + metadata.SupportsUncore { + if flagScope == scopeProcess || flagScope == scopeCgroup { + slog.Debug("Off-core response events not supported in process or cgroup scope", slog.String("event", event.Name)) + return false + } + return true + } + // exclude uncore events when + // - their corresponding device is not found + // - not in system-wide collection scope + if event.Device != "cpu" && event.Device != "" { + if flagScope == scopeProcess || flagScope == scopeCgroup { + slog.Debug("Uncore events not supported in process or cgroup scope", slog.String("event", event.Name)) + return false + } + deviceExists := false + for uncoreDeviceName := range metadata.UncoreDeviceIDs { + if event.Device == uncoreDeviceName { + deviceExists = true + break + } + } + if !deviceExists { + slog.Debug("Uncore device not found", slog.String("device", event.Device)) + return false + } else if !strings.Contains(event.Raw, "umask") && !strings.Contains(event.Raw, "event") { + slog.Debug("Uncore event missing umask or event", slog.String("event", event.Name)) + return false + } + return true + } + // if we got this far, event.Device is empty + // is ref-cycles supported? + if !metadata.SupportsRefCycles && strings.Contains(event.Name, "ref-cycles") { + slog.Debug("ref-cycles not supported on target", slog.String("event", event.Name)) + return false + } + + // no cstate and power events when collecting at process or cgroup scope + if (flagScope == scopeProcess || flagScope == scopeCgroup) && + (strings.Contains(event.Name, "cstate_") || strings.Contains(event.Name, "power/energy")) { + slog.Debug("Cstate and power events not supported in process or cgroup scope", slog.String("event", event.Name)) + return false + } + // finally, if it isn't in the perf list output, it isn't collectable + name := strings.Split(event.Name, ":")[0] + if !strings.Contains(metadata.PerfSupportedEvents, name) { + slog.Debug("Event not supported by perf", slog.String("event", name)) + return false + } + return true +} + +// parseEventDefinition parses one line from the event definition file into a representative structure +func parseEventDefinition(line string) (eventDef EventDefinition, err error) { + eventDef.Raw = line + fields := strings.Split(line, ",") + if len(fields) == 1 { + eventDef.Name = fields[0] + } else if len(fields) > 1 { + nameField := fields[len(fields)-1] + if nameField[:5] != "name=" { + err = fmt.Errorf("unrecognized event format, name field not found: %s", line) + return + } + eventDef.Name = nameField[6 : len(nameField)-2] + eventDef.Device = strings.Split(fields[0], "/")[0] + } else { + err = fmt.Errorf("unrecognized event format: %s", line) + return + } + return +} + +// expandUncoreGroup expands a perf event group into a list of groups where each group is +// associated with an uncore device +func expandUncoreGroup(group GroupDefinition, ids []int, re *regexp.Regexp) (groups []GroupDefinition, err error) { + for _, deviceID := range ids { + var newGroup GroupDefinition + for _, event := range group { + match := re.FindStringSubmatch(event.Raw) + if len(match) == 0 { + err = fmt.Errorf("unexpected raw event format: %s", event.Raw) + return + } + var newEvent EventDefinition + newEvent.Name = fmt.Sprintf("%s.%d", match[4], deviceID) + newEvent.Raw = fmt.Sprintf("uncore_%s_%d/event=%s,umask=%s,name='%s'/", match[1], deviceID, match[2], match[3], newEvent.Name) + newEvent.Device = event.Device + newGroup = append(newGroup, newEvent) + } + groups = append(groups, newGroup) + } + return +} + +// expandUncoreGroups expands groups with uncore events to include events for all uncore devices +// assumes that uncore device events are in their own groups, not mixed with other device types +func expandUncoreGroups(groups []GroupDefinition, metadata Metadata) (expandedGroups []GroupDefinition, err error) { + // example 1: cha/event=0x35,umask=0xc80ffe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_CRD'/, + // expand to: uncore_cha_0/event=0x35,umask=0xc80ffe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_CRD.0'/, + // example 2: cha/event=0x36,umask=0x21,config1=0x4043300000000,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40433'/ + // expand to: uncore_cha_0/event=0x36,umask=0x21,config1=0x4043300000000,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40433'/ + re := regexp.MustCompile(`(\w+)/event=(0x[0-9,a-f,A-F]+),umask=(0x[0-9,a-f,A-F]+.*),name='(.*)'`) + var deviceTypes []string + for deviceType := range metadata.UncoreDeviceIDs { + deviceTypes = append(deviceTypes, deviceType) + } + for _, group := range groups { + device := group[0].Device + if util.StringInList(device, deviceTypes) { + var newGroups []GroupDefinition + if len(metadata.UncoreDeviceIDs[device]) == 0 { + slog.Warn("No uncore devices found", slog.String("type", device)) + continue + } + if newGroups, err = expandUncoreGroup(group, metadata.UncoreDeviceIDs[device], re); err != nil { + return + } + expandedGroups = append(expandedGroups, newGroups...) + } else { + expandedGroups = append(expandedGroups, group) + } + } + return +} diff --git a/cmd/metrics/event_frame.go b/cmd/metrics/event_frame.go new file mode 100644 index 0000000..5a4a2f6 --- /dev/null +++ b/cmd/metrics/event_frame.go @@ -0,0 +1,356 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// Linux perf event output, i.e., from 'perf stat' parsing and processing helper functions + +import ( + "encoding/json" + "fmt" + "log/slog" + "math" + "strconv" + "strings" + + "perfspect/internal/util" + + "golang.org/x/exp/slices" +) + +// EventGroup represents a group of perf events and their values +type EventGroup struct { + EventValues map[string]float64 // event name -> event value + GroupID int + Percentage float64 +} + +// EventFrame represents the list of EventGroups collected with a specific timestamp +// and sometimes present cgroup +type EventFrame struct { + EventGroups []EventGroup + Timestamp float64 + Socket string + CPU string + Cgroup string +} + +// Event represents the structure of an event output by perf stat...with +// a few exceptions +type Event struct { + Interval float64 `json:"interval"` + CPU string `json:"cpu"` + CounterValue string `json:"counter-value"` + Unit string `json:"unit"` + Cgroup string `json:"cgroup"` + Event string `json:"event"` + EventRuntime int `json:"event-runtime"` + PcntRunning float64 `json:"pcnt-running"` + Value float64 // parsed value + Group int // event group index + Socket string // only relevant if granularity is socket +} + +// GetEventFrames organizes raw events received from perf into one or more frames (groups of events) that +// will be used for calculating metrics. +// +// The raw events received from perf will differ based on the scope of collection. Current options +// are system-wide, process, cgroup(s). Cgroup scoped data is received intermixed, i.e., multiple +// cgroups' data is represented in the rawEvents list. Process scoped data is received for only +// one process at a time. +// +// The frames produced will differ based on the intended metric granularity. Current options are +// system, socket, cpu (thread/logical CPU), but only when in system scope. Process and cgroup scope +// only support system-level granularity. +func GetEventFrames(rawEvents [][]byte, eventGroupDefinitions []GroupDefinition, scope string, granularity string, metadata Metadata) (eventFrames []EventFrame, err error) { + // parse raw events into list of Event + var allEvents []Event + if allEvents, err = parseEvents(rawEvents, eventGroupDefinitions); err != nil { + return + } + // coalesce events to one or more lists based on scope and granularity + var coalescedEvents [][]Event + if coalescedEvents, err = coalesceEvents(allEvents, scope, granularity, metadata); err != nil { + return + } + // create one EventFrame per list of Events + for _, events := range coalescedEvents { + // organize events into groups + group := EventGroup{EventValues: make(map[string]float64)} + var lastGroupID int + var eventFrame EventFrame + for eventIdx, event := range events { + if eventIdx == 0 { + lastGroupID = event.Group + eventFrame.Timestamp = event.Interval + if flagGranularity == granularityCPU { + eventFrame.CPU = event.CPU + } else if flagGranularity == granularitySocket { + eventFrame.Socket = event.Socket + } + if flagScope == scopeCgroup { + eventFrame.Cgroup = event.Cgroup + } + } + if event.Group != lastGroupID { + eventFrame.EventGroups = append(eventFrame.EventGroups, group) + group = EventGroup{EventValues: make(map[string]float64)} + lastGroupID = event.Group + } + group.GroupID = event.Group + group.Percentage = event.PcntRunning + group.EventValues[event.Event] = event.Value + } + // add the last group + eventFrame.EventGroups = append(eventFrame.EventGroups, group) + // TODO: can we collapse uncore groups as we're parsing (above)? + if eventFrame, err = collapseUncoreGroupsInFrame(eventFrame); err != nil { + return + } + eventFrames = append(eventFrames, eventFrame) + } + return +} + +// parseEvents parses the raw event data into a list of Event +func parseEvents(rawEvents [][]byte, eventGroupDefinitions []GroupDefinition) (events []Event, err error) { + events = make([]Event, 0, len(rawEvents)) + groupIdx := 0 + eventIdx := -1 + previousEvent := "" + for _, rawEvent := range rawEvents { + var event Event + if event, err = parseEventJSON(rawEvent); err != nil { + err = fmt.Errorf("failed to parse perf event: %v", err) + return + } + if event.Event != previousEvent { + eventIdx++ + previousEvent = event.Event + } + if eventIdx == len(eventGroupDefinitions[groupIdx]) { // last event in group + groupIdx++ + if groupIdx == len(eventGroupDefinitions) { + if flagScope == scopeCgroup { + groupIdx = 0 + } else { + err = fmt.Errorf("event group definitions not aligning with raw events") + return + } + } + eventIdx = 0 + } + event.Group = groupIdx + events = append(events, event) + } + return +} + +// coalesceEvents separates the events into a number of event lists by granularity and scope +func coalesceEvents(allEvents []Event, scope string, granularity string, metadata Metadata) (coalescedEvents [][]Event, err error) { + if scope == scopeSystem { + if granularity == granularitySystem { + coalescedEvents = append(coalescedEvents, allEvents) + return + } else if granularity == granularitySocket { + // one list of Events per Socket + newEvents := make([][]Event, metadata.SocketCount) + for i := 0; i < metadata.SocketCount; i++ { + newEvents[i] = make([]Event, 0, len(allEvents)/metadata.SocketCount) + } + // merge + prevSocket := -1 + var socket int + var newEvent Event + for i, event := range allEvents { + var cpu int + if cpu, err = strconv.Atoi(event.CPU); err != nil { + return + } + socket = metadata.CPUSocketMap[cpu] + if socket != prevSocket { + if i != 0 { + newEvents[prevSocket] = append(newEvents[prevSocket], newEvent) + } + prevSocket = socket + newEvent = event + newEvent.Socket = fmt.Sprintf("%d", socket) + continue + } + newEvent.Value += event.Value + } + newEvents[socket] = append(newEvents[socket], newEvent) + coalescedEvents = append(coalescedEvents, newEvents...) + return + } else if granularity == granularityCPU { + // create one list of Events per CPU + numCPUs := metadata.SocketCount * metadata.CoresPerSocket * metadata.ThreadsPerCore + // note: if some cores have been off-lined, this may cause an issue because 'perf' seems + // to still report events for those cores + newEvents := make([][]Event, numCPUs) + for i := 0; i < numCPUs; i++ { + newEvents[i] = make([]Event, 0, len(allEvents)/numCPUs) + } + for _, event := range allEvents { + var cpu int + if cpu, err = strconv.Atoi(event.CPU); err != nil { + return + } + // handle case where perf returns events for off-lined cores + if cpu > len(newEvents)-1 { + cpusToAdd := len(newEvents) + 1 - cpu + for i := 0; i < cpusToAdd; i++ { + newEvents = append(newEvents, make([]Event, 0, len(allEvents)/numCPUs)) + } + } + newEvents[cpu] = append(newEvents[cpu], event) + } + coalescedEvents = append(coalescedEvents, newEvents...) + } else { + err = fmt.Errorf("unsupported granularity: %s", granularity) + return + } + } else if scope == scopeProcess { + coalescedEvents = append(coalescedEvents, allEvents) + return + } else if scope == scopeCgroup { + // expand events list to one list per cgroup + var allCgroupEvents [][]Event + var cgroups []string + for _, event := range allEvents { + var cgroupIdx int + if cgroupIdx, err = util.StringIndexInList(event.Cgroup, cgroups); err != nil { + cgroups = append(cgroups, event.Cgroup) + cgroupIdx = len(cgroups) - 1 + allCgroupEvents = append(allCgroupEvents, []Event{}) + } + allCgroupEvents[cgroupIdx] = append(allCgroupEvents[cgroupIdx], event) + } + coalescedEvents = append(coalescedEvents, allCgroupEvents...) + } else { + err = fmt.Errorf("unsupported scope: %s", scope) + return + } + return +} + +// collapseUncoreGroupsInFrame merges repeated (per-device) uncore groups into a single +// group by summing the values for events that only differ by device ID. +// +// uncore events are received in repeated perf groups like this: +// group: +// 5.005032332,49,,UNC_CHA_TOR_INSERTS.IA_MISS_CRD.0,2806917160,25.00,, +// 5.005032332,2720,,UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE.0,2806917160,25.00,, +// 5.005032332,1061494,,UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE.0,2806917160,25.00,, +// group: +// 5.005032332,49,,UNC_CHA_TOR_INSERTS.IA_MISS_CRD.1,2806585867,25.00,, +// 5.005032332,2990,,UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE.1,2806585867,25.00,, +// 5.005032332,1200063,,UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE.1,2806585867,25.00,, +// +// For the example above, we will have this: +// 5.005032332,98,,UNC_CHA_TOR_INSERTS.IA_MISS_CRD,2806585867,25.00,, +// 5.005032332,5710,,UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE,2806585867,25.00,, +// 5.005032332,2261557,,UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE,2806585867,25.00,, +// Note: uncore event names start with "UNC" +// Note: we assume that uncore events are not mixed into groups that have other event types, e.g., cpu events +func collapseUncoreGroupsInFrame(inFrame EventFrame) (outFrame EventFrame, err error) { + outFrame = inFrame + outFrame.EventGroups = []EventGroup{} + var idxUncoreMatches []int + for inGroupIdx, inGroup := range inFrame.EventGroups { + // skip groups that have been collapsed + if slices.Contains(idxUncoreMatches, inGroupIdx) { + continue + } + idxUncoreMatches = []int{} + foundUncore := false + for eventName := range inGroup.EventValues { + // only check the first entry + if strings.HasPrefix(eventName, "UNC") { + foundUncore = true + } + break + } + if foundUncore { + // we need to know how many of the following groups (if any) match the current group + // so they can be merged together into a single group + for i := inGroupIdx + 1; i < len(inFrame.EventGroups); i++ { + if isMatchingGroup(inGroup, inFrame.EventGroups[i]) { + // keep track of the groups that match so we can skip processing them since + // they will be merged into a single group + idxUncoreMatches = append(idxUncoreMatches, i) + } else { + break + } + } + var outGroup EventGroup + if outGroup, err = collapseUncoreGroups(inFrame.EventGroups, inGroupIdx, len(idxUncoreMatches)); err != nil { + return + } + outFrame.EventGroups = append(outFrame.EventGroups, outGroup) + } else { + outFrame.EventGroups = append(outFrame.EventGroups, inGroup) + } + } + return +} + +// isMatchingGroup - groups are considered matching if they include the same event names (ignoring .ID suffix) +func isMatchingGroup(groupA, groupB EventGroup) bool { + if len(groupA.EventValues) != len(groupB.EventValues) { + return false + } + aNames := make([]string, 0, len(groupA.EventValues)) + bNames := make([]string, 0, len(groupB.EventValues)) + for eventAName := range groupA.EventValues { + parts := strings.Split(eventAName, ".") + newName := strings.Join(parts[:len(parts)-1], ".") + aNames = append(aNames, newName) + } + for eventBName := range groupB.EventValues { + parts := strings.Split(eventBName, ".") + newName := strings.Join(parts[:len(parts)-1], ".") + bNames = append(bNames, newName) + } + slices.Sort(aNames) + slices.Sort(bNames) + for nameIdx, name := range aNames { + if name != bNames[nameIdx] { + return false + } + } + return true +} + +// collapseUncoreGroups collapses a list of groups into a single group +func collapseUncoreGroups(inGroups []EventGroup, firstIdx int, count int) (outGroup EventGroup, err error) { + outGroup.GroupID = inGroups[firstIdx].GroupID + outGroup.Percentage = inGroups[firstIdx].Percentage + outGroup.EventValues = make(map[string]float64) + for i := firstIdx; i <= firstIdx+count; i++ { + for name, value := range inGroups[i].EventValues { + parts := strings.Split(name, ".") + newName := strings.Join(parts[:len(parts)-1], ".") + if _, ok := outGroup.EventValues[newName]; !ok { + outGroup.EventValues[newName] = 0 + } + outGroup.EventValues[newName] += value + } + } + return +} + +// parseEventJSON parses JSON formatted event into struct +// example: {"interval" : 5.005113019, "cpu": "0", "counter-value" : "22901873.000000", "unit" : "", "cgroup" : "...1cb2de.scope", "event" : "L1D.REPLACEMENT", "event-runtime" : 80081151765, "pcnt-running" : 6.00, "metric-value" : 0.000000, "metric-unit" : "(null)"} +func parseEventJSON(rawEvent []byte) (event Event, err error) { + if err = json.Unmarshal(rawEvent, &event); err != nil { + err = fmt.Errorf("unrecognized event format: \"%s\"", rawEvent) + return + } + if event.Value, err = strconv.ParseFloat(event.CounterValue, 64); err != nil { + event.Value = math.NaN() + err = nil + slog.Debug("failed to parse event value", slog.String("event", string(rawEvent))) + } + return +} diff --git a/cmd/metrics/metadata.go b/cmd/metrics/metadata.go new file mode 100644 index 0000000..e41df7c --- /dev/null +++ b/cmd/metrics/metadata.go @@ -0,0 +1,544 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// defines a structure and a loading funciton to hold information about the platform to be +// used during data collection and metric production + +import ( + "encoding/json" + "fmt" + "log/slog" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + + "perfspect/internal/cpudb" + "perfspect/internal/script" + "perfspect/internal/target" +) + +// Metadata is the representation of the platform's state and capabilities +type Metadata struct { + CoresPerSocket int + CPUSocketMap map[int]int + UncoreDeviceIDs map[string][]int + KernelVersion string + Architecture string + Vendor string + Microarchitecture string + ModelName string + PerfSupportedEvents string + PMUDriverVersion string + SocketCount int + SupportsFixedCycles bool + SupportsFixedInstructions bool + SupportsFixedTMA bool + SupportsRefCycles bool + SupportsUncore bool + ThreadsPerCore int + TSC int + TSCFrequencyHz int +} + +// LoadMetadata - populates and returns a Metadata structure containing state of the +// system. +func LoadMetadata(myTarget target.Target, noRoot bool, perfPath string, localTempDir string) (metadata Metadata, err error) { + // CPU Info + var cpuInfo []map[string]string + cpuInfo, err = getCPUInfo(myTarget) + if err != nil || len(cpuInfo) < 1 { + err = fmt.Errorf("failed to read cpu info: %v", err) + return + } + // Core Count (per socket) + metadata.CoresPerSocket, err = strconv.Atoi(cpuInfo[0]["cpu cores"]) + if err != nil || metadata.CoresPerSocket == 0 { + err = fmt.Errorf("failed to retrieve cores per socket: %v", err) + return + } + // Socket Count + var maxPhysicalID int + if maxPhysicalID, err = strconv.Atoi(cpuInfo[len(cpuInfo)-1]["physical id"]); err != nil { + err = fmt.Errorf("failed to retrieve max physical id: %v", err) + return + } + metadata.SocketCount = maxPhysicalID + 1 + // Hyperthreading - threads per core + if cpuInfo[0]["siblings"] != cpuInfo[0]["cpu cores"] { + metadata.ThreadsPerCore = 2 + } else { + metadata.ThreadsPerCore = 1 + } + // CPUSocketMap + metadata.CPUSocketMap = createCPUSocketMap(metadata.CoresPerSocket, metadata.SocketCount, metadata.ThreadsPerCore == 2) + // Model Name + metadata.ModelName = cpuInfo[0]["model name"] + // Architecture + metadata.Architecture, err = myTarget.GetArchitecture() + if err != nil { + err = fmt.Errorf("failed to retrieve architecture: %v", err) + return + } + // Vendor + metadata.Vendor = cpuInfo[0]["vendor_id"] + // CPU microarchitecture + cpuDb := cpudb.NewCPUDB() + cpu, err := cpuDb.GetCPU(cpuInfo[0]["cpu family"], cpuInfo[0]["model"], cpuInfo[0]["stepping"], "", "", "") + if err != nil { + return + } + metadata.Microarchitecture = cpu.MicroArchitecture + + // PMU driver version + metadata.PMUDriverVersion, err = getPMUDriverVersion(myTarget, localTempDir) + if err != nil { + err = fmt.Errorf("failed to retrieve PMU driver version: %v", err) + return + } + // reduce startup time by running the three perf commands in their own threads + slowFuncChannel := make(chan error) + // perf list + go func() { + var err error + if metadata.PerfSupportedEvents, err = getPerfSupportedEvents(myTarget, perfPath); err != nil { + err = fmt.Errorf("failed to load perf list: %v", err) + } + slowFuncChannel <- err + }() + // ref_cycles + go func() { + var err error + var output string + if metadata.SupportsRefCycles, output, err = getSupportsRefCycles(myTarget, noRoot, perfPath, localTempDir); err != nil { + err = fmt.Errorf("failed to determine if ref_cycles is supported: %v", err) + } else { + if !metadata.SupportsRefCycles { + slog.Warn("ref-cycles not supported", slog.String("output", output)) + } + } + slowFuncChannel <- err + }() + // Fixed-counter TMA events + go func() { + var err error + var output string + if metadata.SupportsFixedTMA, output, err = getSupportsFixedTMA(myTarget, noRoot, perfPath, localTempDir); err != nil { + err = fmt.Errorf("failed to determine if fixed-counter TMA is supported: %v", err) + } else { + if !metadata.SupportsFixedTMA { + slog.Warn("Fixed-counter TMA events not supported", slog.String("output", output)) + } + } + slowFuncChannel <- err + }() + // Fixed-counter cycles events + go func() { + var err error + var output string + if metadata.SupportsFixedCycles, output, err = getSupportsFixedEvent(myTarget, "cpu-cycles", cpu.MicroArchitecture, noRoot, perfPath, localTempDir); err != nil { + err = fmt.Errorf("failed to determine if fixed-counter cpu-cycles is supported: %v", err) + } else { + if !metadata.SupportsFixedCycles { + slog.Warn("Fixed-counter cpu-cycles events not supported", slog.String("output", output)) + } + } + slowFuncChannel <- err + }() + // Fixed-counter instructions events + go func() { + var err error + var output string + if metadata.SupportsFixedInstructions, output, err = getSupportsFixedEvent(myTarget, "instructions", cpu.MicroArchitecture, noRoot, perfPath, localTempDir); err != nil { + err = fmt.Errorf("failed to determine if fixed-counter instructions is supported: %v", err) + } else { + if !metadata.SupportsFixedInstructions { + slog.Warn("Fixed-counter instructions events not supported", slog.String("output", output)) + } + } + slowFuncChannel <- err + }() + defer func() { + var errs []error + errs = append(errs, <-slowFuncChannel) + errs = append(errs, <-slowFuncChannel) + errs = append(errs, <-slowFuncChannel) + errs = append(errs, <-slowFuncChannel) + errs = append(errs, <-slowFuncChannel) + for _, errInside := range errs { + if errInside != nil { + if err == nil { + err = errInside + } else { + err = fmt.Errorf("%v : %v", err, errInside) + } + } + } + }() + // System TSC Frequency + metadata.TSCFrequencyHz, err = getTSCFreqHz(myTarget, localTempDir) + if err != nil { + err = fmt.Errorf("failed to retrieve TSC frequency: %v", err) + return + } + // calculate TSC + metadata.TSC = metadata.SocketCount * metadata.CoresPerSocket * metadata.ThreadsPerCore * metadata.TSCFrequencyHz + // uncore device IDs + if metadata.UncoreDeviceIDs, err = getUncoreDeviceIDs(myTarget, localTempDir); err != nil { + return + } + for uncoreDeviceName := range metadata.UncoreDeviceIDs { + if uncoreDeviceName == "cha" { // could be any uncore device + metadata.SupportsUncore = true + break + } + } + if !metadata.SupportsUncore { + slog.Warn("Uncore devices not supported") + } + // Kernel Version + metadata.KernelVersion, err = getKernelVersion(myTarget, localTempDir) + return +} + +// String - provides a string representation of the Metadata structure +func (md Metadata) String() string { + out := fmt.Sprintf(""+ + "Model Name: %s, "+ + "Architecture: %s, "+ + "Vendor: %s, "+ + "Microarchitecture: %s, "+ + "Socket Count: %d, "+ + "Cores Per Socket: %d, "+ + "Threads per Core: %d, "+ + "TSC Frequency (Hz): %d, "+ + "TSC: %d, "+ + "Fixed cycles slot supported: %t, "+ + "Fixed instructions slot supported: %t, "+ + "Fixed TMA slot supported: %t, "+ + "ref-cycles supported: %t, "+ + "Uncore supported: %t, "+ + "PMU Driver version: %s, "+ + "Kernel version: %s, ", + md.ModelName, + md.Architecture, + md.Vendor, + md.Microarchitecture, + md.SocketCount, + md.CoresPerSocket, + md.ThreadsPerCore, + md.TSCFrequencyHz, + md.TSC, + md.SupportsFixedCycles, + md.SupportsFixedInstructions, + md.SupportsFixedTMA, + md.SupportsRefCycles, + md.SupportsUncore, + md.PMUDriverVersion, + md.KernelVersion) + for deviceName, deviceIds := range md.UncoreDeviceIDs { + var ids []string + for _, id := range deviceIds { + ids = append(ids, fmt.Sprintf("%d", id)) + } + out += fmt.Sprintf("%s: [%s] ", deviceName, strings.Join(ids, ",")) + } + return out +} + +// WriteJSONToFile writes the metadata structure (minus perf's supported events) to the filename provided +// Note that the file will be truncated. +func (md Metadata) WriteJSONToFile(path string) (err error) { + var rawFile *os.File + if rawFile, err = os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644); err != nil { + slog.Error("failed to open raw file for writing", slog.String("error", err.Error())) + return + } + defer rawFile.Close() + var out []byte + mdCopy := md + mdCopy.PerfSupportedEvents = "" + if out, err = json.Marshal(mdCopy); err != nil { + slog.Error("failed to marshal metadata structure", slog.String("error", err.Error())) + return + } + out = append(out, []byte("\n")...) + if _, err = rawFile.Write(out); err != nil { + slog.Error("failed to write metadata json to file", slog.String("error", err.Error())) + return + } + return +} + +// getUncoreDeviceIDs - returns a map of device type to list of device indices +// e.g., "upi" -> [0,1,2,3], +func getUncoreDeviceIDs(myTarget target.Target, localTempDir string) (IDs map[string][]int, err error) { + scriptDef := script.ScriptDefinition{ + Name: "list uncore devices", + Script: "find /sys/bus/event_source/devices/ \\( -name uncore_* -o -name amd_* \\)", + Superuser: false, + } + scriptOutput, err := script.RunScript(myTarget, scriptDef, localTempDir) + if err != nil { + err = fmt.Errorf("failed to list uncore devices: %s, %d, %v", scriptOutput.Stderr, scriptOutput.Exitcode, err) + return + } + fileNames := strings.Split(scriptOutput.Stdout, "\n") + IDs = make(map[string][]int) + re := regexp.MustCompile(`(?:uncore_|amd_)(.*)_(\d+)`) + for _, fileName := range fileNames { + match := re.FindStringSubmatch(fileName) + if match == nil { + continue + } + var id int + if id, err = strconv.Atoi(match[2]); err != nil { + return + } + IDs[match[1]] = append(IDs[match[1]], id) + } + return +} + +// getCPUInfo - reads and returns all data from /proc/cpuinfo +func getCPUInfo(myTarget target.Target) (cpuInfo []map[string]string, err error) { + cmd := exec.Command("cat", "/proc/cpuinfo") + stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0) + if err != nil { + err = fmt.Errorf("failed to get cpuinfo: %s, %d, %v", stderr, exitcode, err) + return + } + oneCPUInfo := make(map[string]string) + for _, line := range strings.Split(stdout, "\n") { + fields := strings.Split(line, ":") + if len(fields) < 2 { + if len(oneCPUInfo) > 0 { + cpuInfo = append(cpuInfo, oneCPUInfo) + oneCPUInfo = make(map[string]string) + continue + } else { + break + } + } + oneCPUInfo[strings.TrimSpace(fields[0])] = strings.TrimSpace(fields[1]) + } + return +} + +// getPerfSupportedEvents - returns a string containing the output from +// 'perf list' +func getPerfSupportedEvents(myTarget target.Target, perfPath string) (supportedEvents string, err error) { + cmd := exec.Command(perfPath, "list") + stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0) + if err != nil { + err = fmt.Errorf("failed to get perf list: %s, %d, %v", stderr, exitcode, err) + return + } + supportedEvents = stdout + return +} + +// getSupportsRefCycles() - checks if the ref-cycles event is supported by perf +// On some VMs, the ref-cycles event is not supported and perf returns an error +func getSupportsRefCycles(myTarget target.Target, noRoot bool, perfPath string, localTempDir string) (supported bool, output string, err error) { + scriptDef := script.ScriptDefinition{ + Name: "perf stat ref-cycles", + Script: perfPath + " stat -a -e ref-cycles sleep .1", + Superuser: !noRoot, + } + scriptOutput, err := script.RunScript(myTarget, scriptDef, localTempDir) + if err != nil { + err = fmt.Errorf("failed to determine if ref-cycles is supported: %s, %d, %v", scriptOutput.Stderr, scriptOutput.Exitcode, err) + return + } + supported = !strings.Contains(scriptOutput.Stderr, "") + return +} + +// getSupportsFixedTMA - checks if the fixed TMA counter events are +// supported by perf. +// +// We check for the TOPDOWN.SLOTS and PERF_METRICS.BAD_SPECULATION events as +// an indicator of support for fixed TMA counter support. At the time of +// writing, these events are not supported on AWS m7i VMs or AWS m6i VMs. On +// AWS m7i VMs, we get an error from the perf stat command below. On AWS m6i +// VMs, the values of the events equal to each other. +// In some other situations (need to find/document) the event count values are +// zero. +// All three of these failure modes are checked for in this function. +func getSupportsFixedTMA(myTarget target.Target, noRoot bool, perfPath string, localTempDir string) (supported bool, output string, err error) { + scriptDef := script.ScriptDefinition{ + Name: "perf stat tma", + Script: perfPath + " stat -a -e '{cpu/event=0x00,umask=0x04,period=10000003,name='TOPDOWN.SLOTS'/,cpu/event=0x00,umask=0x81,period=10000003,name='PERF_METRICS.BAD_SPECULATION'/}' sleep .1", + Superuser: !noRoot, + } + scriptOutput, err := script.RunScript(myTarget, scriptDef, localTempDir) + if err != nil { + // err from perf stat is 1st indication that these events are not supported, so return a nil error + supported = false + output = fmt.Sprint(err) + err = nil + return + } + // event values being zero or equal to each other is 2nd indication that these events are not (properly) supported + output = scriptOutput.Stderr + vals := make(map[string]float64) + lines := strings.Split(scriptOutput.Stderr, "\n") + // example line: " 784333932 TOPDOWN.SLOTS (59.75%)" + re := regexp.MustCompile(`\s+(\d+)\s+(\w*\.*\w*)\s+.*`) + for _, line := range lines { + // count may include commas as thousands separators, remove them + line = strings.ReplaceAll(line, ",", "") + match := re.FindStringSubmatch(line) + if match != nil { + vals[match[2]], err = strconv.ParseFloat(match[1], 64) + if err != nil { + // this should never happen + panic("failed to parse float") + } + } + } + topDownSlots := vals["TOPDOWN.SLOTS"] + badSpeculation := vals["PERF_METRICS.BAD_SPECULATION"] + supported = topDownSlots != badSpeculation && topDownSlots != 0 && badSpeculation != 0 + return +} + +func getSupportsFixedEvent(myTarget target.Target, event string, uarch string, noRoot bool, perfPath string, localTempDir string) (supported bool, output string, err error) { + shortUarch := uarch[:3] + var numGPCounters int + switch shortUarch { + case "BDX": + fallthrough + case "SKX": + fallthrough + case "CLX": + numGPCounters = 4 + case "ICX": + fallthrough + case "SPR": + fallthrough + case "EMR": + fallthrough + case "SRF": + fallthrough + case "GNR": + numGPCounters = 8 + default: + err = fmt.Errorf("unsupported uarch: %s", uarch) + return + } + var eventList []string + for i := 0; i < numGPCounters; i++ { + eventList = append(eventList, event) + } + scriptDef := script.ScriptDefinition{ + Name: "perf stat " + event, + Script: perfPath + " stat -a -e '{" + strings.Join(eventList, ",") + "}' sleep .1", + Superuser: !noRoot, + } + scriptOutput, err := script.RunScript(myTarget, scriptDef, localTempDir) + if err != nil { + supported = false + return + } + output = scriptOutput.Stderr + // on some VMs we see "" or "" in the perf output + if strings.Contains(output, "") || strings.Contains(output, "= totalCPUs/2 { + socket = (i - totalCPUs/2) / coresPerSocket + } + } + // Store the mapping + cpuSocketMap[i] = socket + } + return cpuSocketMap +} diff --git a/cmd/metrics/metric.go b/cmd/metrics/metric.go new file mode 100644 index 0000000..c40589e --- /dev/null +++ b/cmd/metrics/metric.go @@ -0,0 +1,248 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// metric generation type defintions and helper functions + +import ( + "fmt" + "log/slog" + "math" + "os" + "strings" + "sync" + + "github.com/Knetic/govaluate" + mapset "github.com/deckarep/golang-set/v2" +) + +// Metric represents a metric (name, value) derived from perf events +type Metric struct { + Name string + Value float64 +} + +// MetricFrame represents the metrics values and associated metadata +type MetricFrame struct { + Metrics []Metric + Timestamp float64 + FrameCount int + Socket string + CPU string + Cgroup string + PID string + Cmd string +} + +// ProcessEvents is responsible for producing metrics from raw perf events +func ProcessEvents(perfEvents [][]byte, eventGroupDefinitions []GroupDefinition, metricDefinitions []MetricDefinition, processes []Process, previousTimestamp float64, metadata Metadata, outputDir string) (metricFrames []MetricFrame, timeStamp float64, err error) { + var eventFrames []EventFrame + if eventFrames, err = GetEventFrames(perfEvents, eventGroupDefinitions, flagScope, flagGranularity, metadata); err != nil { // arrange the events into groups + err = fmt.Errorf("failed to put perf events into groups: %v", err) + return + } + metricFrames = make([]MetricFrame, 0, len(eventFrames)) + for _, eventFrame := range eventFrames { + timeStamp = eventFrame.Timestamp + var metricFrame MetricFrame + metricFrame.Metrics = make([]Metric, 0, len(metricDefinitions)) + metricFrame.Timestamp = eventFrame.Timestamp + metricFrame.Socket = eventFrame.Socket + metricFrame.CPU = eventFrame.CPU + metricFrame.Cgroup = eventFrame.Cgroup + var pidList []string + var cmdList []string + for _, process := range processes { + pidList = append(pidList, process.pid) + cmdList = append(cmdList, process.cmd) + } + metricFrame.PID = strings.Join(pidList, ",") + metricFrame.Cmd = strings.Join(cmdList, ",") + // produce metrics from event groups + for _, metricDef := range metricDefinitions { + metric := Metric{Name: metricDef.Name, Value: math.NaN()} + var variables map[string]interface{} + if variables, err = getExpressionVariableValues(metricDef, eventFrame, previousTimestamp, metadata); err != nil { + slog.Debug("failed to get expression variable values", slog.String("error", err.Error())) + err = nil + } else { + var result interface{} + if result, err = evaluateExpression(metricDef, variables); err != nil { + slog.Debug("failed to evaluate expression", slog.String("error", err.Error())) + err = nil + } else { + metric.Value = result.(float64) + } + } + metricFrame.Metrics = append(metricFrame.Metrics, metric) + var prettyVars []string + for variableName := range variables { + prettyVars = append(prettyVars, fmt.Sprintf("%s=%f", variableName, variables[variableName])) + } + slog.Debug("processed metric", slog.String("name", metricDef.Name), slog.String("expression", metricDef.Expression), slog.String("vars", strings.Join(prettyVars, ", "))) + } + metricFrames = append(metricFrames, metricFrame) + } + return +} + +// GetEvaluatorFunctions defines functions that can be called in metric expressions +func GetEvaluatorFunctions() (functions map[string]govaluate.ExpressionFunction) { + functions = make(map[string]govaluate.ExpressionFunction) + functions["max"] = func(args ...interface{}) (interface{}, error) { + var leftVal float64 + var rightVal float64 + switch t := args[0].(type) { + case int: + leftVal = float64(t) + case float64: + leftVal = t + } + switch t := args[1].(type) { + case int: + rightVal = float64(t) + case float64: + rightVal = t + } + return max(leftVal, rightVal), nil + } + functions["min"] = func(args ...interface{}) (interface{}, error) { + var leftVal float64 + var rightVal float64 + switch t := args[0].(type) { + case int: + leftVal = float64(t) + case float64: + leftVal = t + } + switch t := args[1].(type) { + case int: + rightVal = float64(t) + case float64: + rightVal = t + } + return min(leftVal, rightVal), nil + } + return +} + +// lock to protect metric variable map that holds the event group where a variable value will be retrieved +var metricVariablesLock = sync.RWMutex{} + +// for each variable in a metric, set the best group from which to get its value +func loadMetricBestGroups(metric MetricDefinition, frame EventFrame) (err error) { + // one thread at a time through this function, since it updates the metric variables map and this only needs to be done one time + metricVariablesLock.Lock() + defer metricVariablesLock.Unlock() + // only load event groups one time for each metric + loadGroups := false + for variableName := range metric.Variables { + if metric.Variables[variableName] == -1 { // group not yet set + loadGroups = true + break + } + if metric.Variables[variableName] == -2 { // tried previously and failed, don't try again + err = fmt.Errorf("metric variable group assignment previously failed, skipping: %s", variableName) + return + } + } + if !loadGroups { + return // nothing to do, already loaded + } + allVariableNames := mapset.NewSetFromMapKeys(metric.Variables) + remainingVariableNames := allVariableNames.Clone() + for { + if remainingVariableNames.Cardinality() == 0 { // found matches for all + break + } + // find group with the greatest number of event names that match the remaining variable names + bestGroupIdx := -1 + bestMatches := 0 + var matchedNames mapset.Set[string] // := mapset.NewSet([]string{}...) + for groupIdx, group := range frame.EventGroups { + groupEventNames := mapset.NewSetFromMapKeys(group.EventValues) + intersection := remainingVariableNames.Intersect(groupEventNames) + if intersection.Cardinality() > bestMatches { + bestGroupIdx = groupIdx + bestMatches = intersection.Cardinality() + matchedNames = intersection.Clone() + if bestMatches == remainingVariableNames.Cardinality() { + break + } + } + } + if bestGroupIdx == -1 { // no matches + for _, variableName := range remainingVariableNames.ToSlice() { + metric.Variables[variableName] = -2 // we tried and failed + } + err = fmt.Errorf("metric variables (%s) not found for metric: %s", strings.Join(remainingVariableNames.ToSlice(), ", "), metric.Name) + break + } + // for each of the matched names, set the value and the group from which to retrieve the value next time + for _, name := range matchedNames.ToSlice() { + metric.Variables[name] = bestGroupIdx + } + remainingVariableNames = remainingVariableNames.Difference(matchedNames) + } + return +} + +// get the variable values that will be used to evaluate the metric's expression +func getExpressionVariableValues(metric MetricDefinition, frame EventFrame, previousTimestamp float64, metadata Metadata) (variables map[string]interface{}, err error) { + variables = make(map[string]interface{}) + if err = loadMetricBestGroups(metric, frame); err != nil { + err = fmt.Errorf("at least one of the variables couldn't be assigned to a group: %v", err) + return + } + // set the variable values to be used in the expression evaluation + for variableName := range metric.Variables { + if metric.Variables[variableName] == -2 { + err = fmt.Errorf("variable value set to -2 (shouldn't happen): %s", variableName) + return + } + // set the variable value to the event value divided by the perf collection time to normalize the value to 1 second + if len(frame.EventGroups) <= metric.Variables[variableName] { + err = fmt.Errorf("event groups have changed") + return + } + variables[variableName] = frame.EventGroups[metric.Variables[variableName]].EventValues[variableName] / (frame.Timestamp - previousTimestamp) + // adjust cstate_core/c6-residency value if hyperthreading is enabled + // why here? so we don't have to change the perfmon metric formula + if metadata.ThreadsPerCore > 1 && variableName == "cstate_core/c6-residency/" { + variables[variableName] = variables[variableName].(float64) * float64(metadata.ThreadsPerCore) + } + } + return +} + +// function to call evaluator so that we can catch panics that come from the evaluator +func evaluateExpression(metric MetricDefinition, variables map[string]interface{}) (result interface{}, err error) { + defer func() { + if errx := recover(); errx != nil { + err = errx.(error) + } + }() + if result, err = metric.Evaluable.Evaluate(variables); err != nil { + err = fmt.Errorf("%v : %s : %s", err, metric.Name, metric.Expression) + } + return +} + +// write json formatted events to raw file +func writeEventsToFile(path string, events [][]byte) (err error) { + var rawFile *os.File + if rawFile, err = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil { + slog.Error("failed to open raw file for writing", slog.String("error", err.Error())) + return + } + defer rawFile.Close() + for _, rawEvent := range events { + rawEvent = append(rawEvent, []byte("\n")...) + if _, err = rawFile.Write(rawEvent); err != nil { + slog.Error("failed to write event to raw file", slog.String("error", err.Error())) + return + } + } + return +} diff --git a/cmd/metrics/metric_defs.go b/cmd/metrics/metric_defs.go new file mode 100644 index 0000000..9264bfd --- /dev/null +++ b/cmd/metrics/metric_defs.go @@ -0,0 +1,240 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "encoding/json" + "fmt" + "log/slog" + "os" + "path/filepath" + "strings" + + "perfspect/internal/util" + + "github.com/Knetic/govaluate" +) + +type Variable struct { + Name string + EventGroupIdx int // initialized to -1 to indicate that a group has not yet been identified +} + +type MetricDefinition struct { + Name string `json:"name"` + Expression string `json:"expression"` + NameTxn string `json:"name-txn"` + ExpressionTxn string `json:"expression-txn"` + Variables map[string]int // parsed from Expression for efficiency, int represents group index + Evaluable *govaluate.EvaluableExpression // parse expression once, store here for use in metric evaluation +} + +// LoadMetricDefinitions reads and parses metric definitions from an architecture-specific metric +// definition file. When the override path argument is empty, the function will load metrics from +// the file associated with the platform's architecture found in the provided metadata. When +// a list of metric names is provided, only those metric definitions will be loaded. +func LoadMetricDefinitions(metricDefinitionOverridePath string, selectedMetrics []string, uncollectableEvents []string, metadata Metadata) (metrics []MetricDefinition, err error) { + var bytes []byte + if metricDefinitionOverridePath != "" { + if bytes, err = os.ReadFile(metricDefinitionOverridePath); err != nil { + return + } + } else { + uarch := strings.ToLower(strings.Split(metadata.Microarchitecture, "_")[0]) + // use alternate events/metrics when TMA fixed counters are not supported + alternate := "" + if (uarch == "icx" || uarch == "spr" || uarch == "emr") && !metadata.SupportsFixedTMA { + alternate = "_nofixedtma" + } + metricFileName := fmt.Sprintf("%s%s.json", uarch, alternate) + if bytes, err = resources.ReadFile(filepath.Join("resources", "metrics", metadata.Architecture, metadata.Vendor, metricFileName)); err != nil { + return + } + } + var metricsInFile []MetricDefinition + if err = json.Unmarshal(bytes, &metricsInFile); err != nil { + return + } + // remove "metric_" prefix from metric names + for i := range metricsInFile { + metricsInFile[i].Name = strings.TrimPrefix(metricsInFile[i].Name, "metric_") + } + // remove metrics from list that use uncollectable events + for _, uncollectableEvent := range uncollectableEvents { + for i := 0; i < len(metricsInFile); i++ { + if strings.Contains(metricsInFile[i].Expression, uncollectableEvent) { + slog.Debug("removing metric that uses uncollectable event", slog.String("metric", metricsInFile[i].Name), slog.String("event", uncollectableEvent)) + metricsInFile = append(metricsInFile[:i], metricsInFile[i+1:]...) + i-- + } + } + } + // if a list of metric names provided, reduce list to match + if len(selectedMetrics) > 0 { + // confirm provided metric names are valid (included in metrics defined in file) + for _, metricName := range selectedMetrics { + found := false + for _, metric := range metricsInFile { + if metricName == metric.Name { + found = true + break + } + } + if !found { + err = fmt.Errorf("provided metric name not found: %s", metricName) + return + } + } + // build list of metrics based on provided list of metric names + for _, metric := range metricsInFile { + if !util.StringInList(metric.Name, selectedMetrics) { + continue + } + metrics = append(metrics, metric) + } + } else { + metrics = metricsInFile + } + return +} + +// ConfigureMetrics prepares metrics for use by the evaluator, by e.g., replacing +// metric constants with known values and aligning metric variables to perf event +// groups +func ConfigureMetrics(metrics []MetricDefinition, evaluatorFunctions map[string]govaluate.ExpressionFunction, metadata Metadata) (err error) { + // get constants as strings + tscFreq := fmt.Sprintf("%f", float64(metadata.TSCFrequencyHz)) + tsc := fmt.Sprintf("%f", float64(metadata.TSC)) + coresPerSocket := fmt.Sprintf("%f", float64(metadata.CoresPerSocket)) + chasPerSocket := fmt.Sprintf("%f", float64(len(metadata.UncoreDeviceIDs["cha"]))) + socketCount := fmt.Sprintf("%f", float64(metadata.SocketCount)) + hyperThreadingOn := fmt.Sprintf("%t", metadata.ThreadsPerCore > 1) + threadsPerCore := fmt.Sprintf("%f", float64(metadata.ThreadsPerCore)) + // configure each metric + for metricIdx := range metrics { + // swap in per-txn metric definition if transaction rate is provided + if flagTransactionRate != 0 && metrics[metricIdx].ExpressionTxn != "" { + metrics[metricIdx].Expression = metrics[metricIdx].ExpressionTxn + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[TXN]", fmt.Sprintf("%f", flagTransactionRate)) + metrics[metricIdx].Name = metrics[metricIdx].NameTxn + } + // transform if/else to ?/: + var transformed string + if transformed, err = transformConditional(metrics[metricIdx].Expression); err != nil { + return + } + if transformed != metrics[metricIdx].Expression { + slog.Debug("transformed metric", slog.String("original", metrics[metricIdx].Name), slog.String("transformed", transformed)) + metrics[metricIdx].Expression = transformed + } + // replace constants with their values + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[SYSTEM_TSC_FREQ]", tscFreq) + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[TSC]", tsc) + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[CORES_PER_SOCKET]", coresPerSocket) + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[CHAS_PER_SOCKET]", chasPerSocket) + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[SOCKET_COUNT]", socketCount) + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[HYPERTHREADING_ON]", hyperThreadingOn) + metrics[metricIdx].Expression = strings.ReplaceAll(metrics[metricIdx].Expression, "[CONST_THREAD_COUNT]", threadsPerCore) + // get a list of the variables in the expression + metrics[metricIdx].Variables = make(map[string]int) + expressionIdx := 0 + for { + startVar := strings.IndexRune(metrics[metricIdx].Expression[expressionIdx:], '[') + if startVar == -1 { // no more vars in this expression + break + } + endVar := strings.IndexRune(metrics[metricIdx].Expression[expressionIdx:], ']') + if endVar == -1 { + err = fmt.Errorf("didn't find end of variable indicator (]) in expression: %s", metrics[metricIdx].Expression[expressionIdx:]) + return + } + // add the variable name to the map, set group index to -1 to indicate it has not yet been determined + metrics[metricIdx].Variables[metrics[metricIdx].Expression[expressionIdx:][startVar+1:endVar]] = -1 + expressionIdx += endVar + 1 + } + if metrics[metricIdx].Evaluable, err = govaluate.NewEvaluableExpressionWithFunctions(metrics[metricIdx].Expression, evaluatorFunctions); err != nil { + slog.Error("failed to create evaluable expression for metric", slog.String("error", err.Error()), slog.String("metric name", metrics[metricIdx].Name), slog.String("metric expression", metrics[metricIdx].Expression)) + return + } + } + return +} + +// transformConditional transforms if/else to ternary conditional (? :) so expression evaluator can handle it +// simple: +// from: if else +// to: ? : +// less simple: +// from: (() if else ()) +// to: ( ? () : +func transformConditional(origIn string) (out string, err error) { + numIfs := strings.Count(origIn, "if") + if numIfs == 0 { + out = origIn + return + } + in := origIn + for i := 0; i < numIfs; i++ { + if i > 0 { + in = out + } + var idxIf, idxElse, idxExpression1, idxExpression3 int + if idxIf = strings.Index(in, "if"); idxIf == -1 { + err = fmt.Errorf("didn't find expected if: %s", in) + return + } + if idxElse = strings.Index(in, "else"); idxElse == -1 { + err = fmt.Errorf("if without else in expression: %s", in) + return + } + // find the beginning of expression 1 (also end of expression 0) + var parens int + for i := idxIf - 1; i >= 0; i-- { + c := in[i] + if c == ')' { + parens += 1 + } else if c == '(' { + parens -= 1 + } else { + continue + } + if parens < 0 { + idxExpression1 = i + 1 + break + } + } + // find the end of expression 2 (also beginning of expression 3) + parens = 0 + for i, c := range in[idxElse+5:] { + if c == '(' { + parens += 1 + } else if c == ')' { + parens -= 1 + } else { + continue + } + if parens < 0 { + idxExpression3 = i + idxElse + 6 + break + } + } + if idxExpression3 == 0 { + idxExpression3 = len(in) + } + expression0 := in[:idxExpression1] + expression1 := in[idxExpression1 : idxIf-1] + condition := in[idxIf+3 : idxElse-1] + expression2 := in[idxElse+5 : idxExpression3] + expression3 := in[idxExpression3:] + var space0, space3 string + if expression0 != "" { + space0 = " " + } + if expression3 != "" { + space3 = " " + } + out = fmt.Sprintf("%s%s%s ? %s : %s%s%s", expression0, space0, condition, expression1, expression2, space3, expression3) + } + return +} diff --git a/cmd/metrics/metric_defs_test.go b/cmd/metrics/metric_defs_test.go new file mode 100644 index 0000000..272f1a8 --- /dev/null +++ b/cmd/metrics/metric_defs_test.go @@ -0,0 +1,75 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import "testing" + +func TestTransformConditional(t *testing.T) { + var in string + var out string + var err error + + in = "100 * x / y" + if out, err = transformConditional(in); err != nil { + t.Error(err) + } + if out != in { + t.Error("out should equal in") + } + + in = "100 * x / y if z" + if _, err = transformConditional(in); err == nil { + t.Error("didn't catch if without else") + } + + in = "a if b else c" + if out, err = transformConditional(in); err != nil { + t.Error(err) + } + if out != "b ? a : c" { + t.Errorf("improper transform: [%s] -> [%s]", in, out) + } + + in = "(1 - x / y) if z > 1 else 0" + if out, err = transformConditional(in); err != nil { + t.Error(err) + } + if out != "z > 1 ? (1 - x / y) : 0" { + t.Errorf("improper transform: [%s] -> [%s]", in, out) + } + + in = "1 - a / b if c > 1 else 0" + if out, err = transformConditional(in); err != nil { + t.Error(err) + } + if out != "c > 1 ? 1 - a / b : 0" { + t.Errorf("improper transform: [%s] -> [%s]", in, out) + } + + in = "1 - ( (a) if c else d )" + if out, err = transformConditional(in); err != nil { + t.Error(err) + } + if out != "1 - ( c ? (a) : d )" { + t.Errorf("improper transform: [%s] -> [%s]", in, out) + } + + // from SPR metrics -- metric_TMA_....DRAM_Bound(%) + in = "100 * ( min( ( ( ( a / ( b ) ) - ( min( ( ( ( ( 1 - ( ( ( 19 * ( c * ( 1 + ( d / e ) ) ) + 10 * ( ( f * ( 1 + ( d / e ) ) ) + ( g * ( 1 + ( d / e ) ) ) + ( h * ( 1 + ( d / e ) ) ) ) ) / ( ( 19 * ( c * ( 1 + ( d / e ) ) ) + 10 * ( ( f * ( 1 + ( d / e ) ) ) + ( g * ( 1 + ( d / e ) ) ) + ( h * ( 1 + ( d / e ) ) ) ) ) + ( 25 * ( ( i * ( 1 + ( d / e ) ) ) ) + 33 * ( ( j * ( 1 + ( d / e ) ) ) ) ) ) ) ) ) * ( a / ( b ) ) ) if ( ( 1000000 ) * ( j + i ) > e ) else 0 ) ) , ( 1 ) ) ) ) ) , ( 1 ) ) )" + if out, err = transformConditional(in); err != nil { + t.Error(err) + } + if out != "100 * ( min( ( ( ( a / ( b ) ) - ( min( ( ( ( ( 1000000 ) * ( j + i ) > e ) ? ( ( 1 - ( ( ( 19 * ( c * ( 1 + ( d / e ) ) ) + 10 * ( ( f * ( 1 + ( d / e ) ) ) + ( g * ( 1 + ( d / e ) ) ) + ( h * ( 1 + ( d / e ) ) ) ) ) / ( ( 19 * ( c * ( 1 + ( d / e ) ) ) + 10 * ( ( f * ( 1 + ( d / e ) ) ) + ( g * ( 1 + ( d / e ) ) ) + ( h * ( 1 + ( d / e ) ) ) ) ) + ( 25 * ( ( i * ( 1 + ( d / e ) ) ) ) + 33 * ( ( j * ( 1 + ( d / e ) ) ) ) ) ) ) ) ) * ( a / ( b ) ) ) : 0 ) ) , ( 1 ) ) ) ) ) , ( 1 ) ) )" { + t.Errorf("improper transform: [%s] -> [%s]", in, out) + } + + // from SPR metrics -- metric_TMA_....Ports_Utilization(%) + in = "100 * ( ( a + ( b / ( c ) ) * ( d - e ) + ( f + ( g / ( h + i + g + j ) ) * k ) ) / ( c ) if ( l < ( d - e ) ) else ( f + ( g / ( h + i + g + j ) ) * k ) / ( c ) )" + if out, err = transformConditional(in); err != nil { + t.Error(err) + } + if out != "100 * ( ( l < ( d - e ) ) ? ( a + ( b / ( c ) ) * ( d - e ) + ( f + ( g / ( h + i + g + j ) ) * k ) ) / ( c ) : ( f + ( g / ( h + i + g + j ) ) * k ) / ( c ) )" { + t.Errorf("improper transform: [%s] -> [%s]", in, out) + } +} diff --git a/cmd/metrics/metrics.go b/cmd/metrics/metrics.go new file mode 100644 index 0000000..2d564ea --- /dev/null +++ b/cmd/metrics/metrics.go @@ -0,0 +1,1218 @@ +// Package metrics is a subcommand of the root command. It provides functionality to monitor core and uncore metrics on one target. +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "embed" + "fmt" + "log/slog" + "os" + "os/exec" + "os/signal" + "path" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "perfspect/internal/common" + "perfspect/internal/progress" + "perfspect/internal/script" + "perfspect/internal/target" + "perfspect/internal/util" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +const cmdName = "metrics" + +var examples = []string{ + fmt.Sprintf(" Metrics from local host: $ %s %s --duration 30", common.AppName, cmdName), + fmt.Sprintf(" Metrics from local host in CSV format: $ %s %s --format csv", common.AppName, cmdName), + fmt.Sprintf(" Metrics from remote host: $ %s %s --target 192.168.1.1 --user fred --key fred_key", common.AppName, cmdName), + fmt.Sprintf(" Metrics for \"hot\" processes: $ %s %s --scope process", common.AppName, cmdName), + fmt.Sprintf(" Metrics for specified processes: $ %s %s --scope process --pids 1234,6789", common.AppName, cmdName), + fmt.Sprintf(" Start application and collect metrics: $ %s %s -- /path/to/myapp arg1 arg2", common.AppName, cmdName), + fmt.Sprintf(" Metrics adjusted for transaction rate: $ %s %s --txnrate 100", common.AppName, cmdName), + fmt.Sprintf(" \"Live\" metrics: $ %s %s --live", common.AppName, cmdName), +} + +var Cmd = &cobra.Command{ + Use: cmdName, + Short: "Monitor core and uncore metrics from one target", + Long: "", + Example: strings.Join(examples, "\n"), + RunE: runCmd, + PreRunE: validateFlags, + GroupID: "primary", + Args: cobra.ArbitraryArgs, + SilenceErrors: true, +} + +//go:embed resources +var resources embed.FS + +// globals +var ( + gSignalMutex sync.Mutex + gSignalReceived bool +) + +func setSignalReceived() { + gSignalMutex.Lock() + defer gSignalMutex.Unlock() + gSignalReceived = true +} + +func getSignalReceived() bool { + for i := 0; i < 10; i++ { + gSignalMutex.Lock() + received := gSignalReceived + gSignalMutex.Unlock() + if received { + return true + } + time.Sleep(10 * time.Millisecond) + } + return gSignalReceived +} + +var ( + // collection options + flagDuration int + flagScope string + flagPidList []string + flagCidList []string + flagFilter string + flagCount int + flagRefresh int + // output format options + flagGranularity string + flagOutputFormat []string + flagLive bool + flagTransactionRate float64 + // advanced options + flagShowMetricNames bool + flagMetricsList []string + flagEventFilePath string + flagMetricFilePath string + flagPerfPrintInterval int + flagPerfMuxInterval int + flagNoRoot bool + flagWriteEventsToFile bool + + // positional arguments + argsApplication []string +) + +const ( + flagDurationName = "duration" + flagScopeName = "scope" + flagPidListName = "pids" + flagCidListName = "cids" + flagFilterName = "filter" + flagCountName = "count" + flagRefreshName = "refresh" + + flagGranularityName = "granularity" + flagOutputFormatName = "format" + flagLiveName = "live" + flagTransactionRateName = "txnrate" + + flagShowMetricNamesName = "list" + flagMetricsListName = "metrics" + flagEventFilePathName = "eventfile" + flagMetricFilePathName = "metricfile" + flagPerfPrintIntervalName = "interval" + flagPerfMuxIntervalName = "muxinterval" + flagNoRootName = "noroot" + flagWriteEventsToFileName = "raw" +) + +var gCollectionStartTime time.Time + +const ( + granularitySystem = "system" + granularitySocket = "socket" + granularityCPU = "cpu" +) + +var granularityOptions = []string{granularitySystem, granularitySocket, granularityCPU} + +const ( + scopeSystem = "system" + scopeProcess = "process" + scopeCgroup = "cgroup" +) + +var scopeOptions = []string{scopeSystem, scopeProcess, scopeCgroup} + +const ( + formatTxt = "txt" + formatCSV = "csv" + formatJSON = "json" + formatWide = "wide" +) + +var formatOptions = []string{formatTxt, formatCSV, formatJSON, formatWide} + +func init() { + Cmd.Flags().IntVar(&flagDuration, flagDurationName, 0, "") + Cmd.Flags().StringVar(&flagScope, flagScopeName, scopeSystem, "") + Cmd.Flags().StringSliceVar(&flagPidList, flagPidListName, []string{}, "") + Cmd.Flags().StringSliceVar(&flagCidList, flagCidListName, []string{}, "") + Cmd.Flags().StringVar(&flagFilter, flagFilterName, "", "") + Cmd.Flags().IntVar(&flagCount, flagCountName, 5, "") + Cmd.Flags().IntVar(&flagRefresh, flagRefreshName, 30, "") + + Cmd.Flags().StringVar(&flagGranularity, flagGranularityName, granularitySystem, "") + Cmd.Flags().StringSliceVar(&flagOutputFormat, flagOutputFormatName, []string{formatCSV}, "") + Cmd.Flags().BoolVar(&flagLive, flagLiveName, false, "") + Cmd.Flags().Float64Var(&flagTransactionRate, flagTransactionRateName, 0, "") + + Cmd.Flags().BoolVar(&flagShowMetricNames, flagShowMetricNamesName, false, "") + Cmd.Flags().StringSliceVar(&flagMetricsList, flagMetricsListName, []string{}, "") + Cmd.Flags().StringVar(&flagEventFilePath, flagEventFilePathName, "", "") + Cmd.Flags().StringVar(&flagMetricFilePath, flagMetricFilePathName, "", "") + Cmd.Flags().IntVar(&flagPerfPrintInterval, flagPerfPrintIntervalName, 5, "") + Cmd.Flags().IntVar(&flagPerfMuxInterval, flagPerfMuxIntervalName, 125, "") + Cmd.Flags().BoolVar(&flagNoRoot, flagNoRootName, false, "") + Cmd.Flags().BoolVar(&flagWriteEventsToFile, flagWriteEventsToFileName, false, "") + + common.AddTargetFlags(Cmd) + + Cmd.SetUsageFunc(usageFunc) +} + +func usageFunc(cmd *cobra.Command) error { + cmd.Printf("Usage: %s [flags] [-- application args]\n\n", cmd.CommandPath()) + cmd.Printf("Examples:\n%s\n\n", cmd.Example) + cmd.Println("Arguments:") + cmd.Printf(" application (optional): path to an application to run and collect metrics for\n\n") + cmd.Println("Flags:") + for _, group := range getFlagGroups() { + cmd.Printf(" %s:\n", group.GroupName) + for _, flag := range group.Flags { + flagDefault := "" + if cmd.Flags().Lookup(flag.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(flag.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", flag.Name, flag.Help, flagDefault) + } + } + cmd.Println("\nGlobal Flags:") + cmd.Parent().PersistentFlags().VisitAll(func(pf *pflag.Flag) { + flagDefault := "" + if cmd.Parent().PersistentFlags().Lookup(pf.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(pf.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", pf.Name, pf.Usage, flagDefault) + }) + return nil +} + +func getFlagGroups() []common.FlagGroup { + var groups []common.FlagGroup + // collection options + flags := []common.Flag{ + { + Name: flagDurationName, + Help: "number of seconds to run the collection. If 0, the collection will run indefinitely.", + }, + { + Name: flagScopeName, + Help: fmt.Sprintf("scope of collection, options: %s", strings.Join(scopeOptions, ", ")), + }, + { + Name: flagPidListName, + Help: "comma separated list of process ids. If not provided while collecting in process scope, \"hot\" processes will be monitored.", + }, + { + Name: flagCidListName, + Help: "comma separated list of cids. If not provided while collecting at cgroup scope, \"hot\" cgroups will be monitored.", + }, + { + Name: flagFilterName, + Help: "regular expression used to match process names or cgroup IDs when in process or cgroup scope and when --pids or --cids are not specified", + }, + { + Name: flagCountName, + Help: "maximum number of \"hot\" or \"filtered\" processes or cgroups to monitor", + }, + { + Name: flagRefreshName, + Help: "number of seconds to run before refreshing the \"hot\" or \"filtered\" process or cgroup list", + }, + } + groups = append(groups, common.FlagGroup{ + GroupName: "Collection Options", + Flags: flags, + }) + // output options + flags = []common.Flag{ + { + Name: flagGranularityName, + Help: fmt.Sprintf("level of metric granularity. Only valid when collecting at system scope. Options: %s.", strings.Join(granularityOptions, ", ")), + }, + { + Name: flagOutputFormatName, + Help: fmt.Sprintf("output formats, options: %s", strings.Join(formatOptions, ", ")), + }, + { + Name: flagLiveName, + Help: fmt.Sprintf("print metrics to stdout in one output format specified with the --%s flag. No metrics files will be written.", flagOutputFormatName), + }, + { + Name: flagTransactionRateName, + Help: "number of transactions per second. Will divide relevant metrics by transactions/second.", + }, + } + groups = append(groups, common.FlagGroup{ + GroupName: "Output Options", + Flags: flags, + }) + // advanced options + flags = []common.Flag{ + { + Name: flagShowMetricNamesName, + Help: "show metric names available on this platform and exit", + }, + { + Name: flagMetricsListName, + Help: "a comma separated list of quoted metric names to include in output", + }, + { + Name: flagEventFilePathName, + Help: "perf event definition file. Will override default event definitions.", + }, + { + Name: flagMetricFilePathName, + Help: "metric definition file. Will override default metric definitions.", + }, + { + Name: flagPerfPrintIntervalName, + Help: "event collection interval in seconds", + }, + { + Name: flagPerfMuxIntervalName, + Help: "multiplexing interval in milliseconds", + }, + { + Name: flagNoRootName, + Help: "do not elevate to root", + }, + { + Name: flagWriteEventsToFileName, + Help: "write raw perf events to file", + }, + } + groups = append(groups, common.FlagGroup{ + GroupName: "Advanced Options", + Flags: flags, + }) + groups = append(groups, common.GetTargetFlagGroup()) + return groups +} + +func validateFlags(cmd *cobra.Command, args []string) error { + // some flags will not be valid if an application argument is provided + if len(args) > 0 { + argsApplication = args + if flagDuration > 0 { + err := fmt.Errorf("duration is not supported with an application argument") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + if len(flagPidList) > 0 || len(flagCidList) > 0 { + err := fmt.Errorf("pids and cids are not supported with an application argument") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + if flagFilter != "" { + err := fmt.Errorf("filter is not supported with an application argument") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + } + // confirm valid duration + if cmd.Flags().Lookup(flagDurationName).Changed && flagDuration < 0 { + err := fmt.Errorf("duration must be a positive integer") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + if cmd.Flags().Lookup(flagDurationName).Changed && flagDuration != 0 && flagDuration < flagPerfPrintInterval { + err := fmt.Errorf("duration must be greater than or equal to the event collection interval (%ds)", flagPerfPrintInterval) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // confirm valid scope + if cmd.Flags().Lookup(flagScopeName).Changed && !util.StringInList(flagScope, scopeOptions) { + err := fmt.Errorf("invalid scope: %s, valid options are: %s", flagScope, strings.Join(scopeOptions, ", ")) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // pids and cids are mutually exclusive + if len(flagPidList) > 0 && len(flagCidList) > 0 { + err := fmt.Errorf("cannot specify both pids and cids") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // pids only when scope is process + if len(flagPidList) > 0 { + // if scope was set and it wasn't set to process, error + if cmd.Flags().Changed(flagScopeName) && flagScope != scopeProcess { + err := fmt.Errorf("cannot specify pids when scope is not %s", scopeProcess) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // if scope wasn't set, set it to process + flagScope = scopeProcess + // verify PIDs are integers + for _, pid := range flagPidList { + if _, err := strconv.Atoi(pid); err != nil { + err := fmt.Errorf("pids must be integers") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + } + } + // cids only when scope is cgroup + if len(flagCidList) > 0 { + // if scope was set and it wasn't set to cgroup, error + if cmd.Flags().Changed(flagScopeName) && flagScope != scopeCgroup { + err := fmt.Errorf("cannot specify cids when scope is not %s", scopeCgroup) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // if scope wasn't set, set it to cgroup + flagScope = scopeCgroup + } + // filter only no cids or pids + if flagFilter != "" && (len(flagPidList) > 0 || len(flagCidList) > 0) { + err := fmt.Errorf("cannot specify filter when pids or cids are specified") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // filter only when scope is process or cgroup + if flagFilter != "" && (flagScope != scopeProcess && flagScope != scopeCgroup) { + err := fmt.Errorf("cannot specify filter when scope is not %s or %s", scopeProcess, scopeCgroup) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // count must be positive + if flagCount < 1 { + err := fmt.Errorf("count must be a positive integer") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // count only when scope is process or cgroup + if cmd.Flags().Lookup(flagCountName).Changed && flagCount != 5 && (flagScope != scopeProcess && flagScope != scopeCgroup) { + err := fmt.Errorf("cannot specify count when scope is not %s or %s", scopeProcess, scopeCgroup) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // refresh must be greater than perf print interval + if flagRefresh < flagPerfPrintInterval { + err := fmt.Errorf("refresh must be greater than or equal to the event collection interval (%ds)", flagPerfPrintInterval) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + + // output options + // confirm valid granularity + if cmd.Flags().Lookup(flagGranularityName).Changed && !util.StringInList(flagGranularity, granularityOptions) { + err := fmt.Errorf("invalid granularity: %s, valid options are: %s", flagGranularity, strings.Join(granularityOptions, ", ")) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // if scope is not system, granularity must be system + if flagGranularity != granularitySystem && flagScope != scopeSystem { + err := fmt.Errorf("granularity option must be %s when collecting at a scope other than %s", granularitySystem, scopeSystem) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // confirm valid output format + for _, format := range flagOutputFormat { + if !util.StringInList(format, formatOptions) { + err := fmt.Errorf("invalid output format: %s, valid options are: %s", format, strings.Join(formatOptions, ", ")) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + } + // advanced options + // confirm valid perf print interval + if cmd.Flags().Lookup(flagPerfPrintIntervalName).Changed && flagPerfPrintInterval < 1 { + err := fmt.Errorf("event collection interval must be at least 1 second") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // confirm valid perf mux interval + if cmd.Flags().Lookup(flagPerfMuxIntervalName).Changed && flagPerfMuxInterval < 10 { + err := fmt.Errorf("mux interval must be at least 10 milliseconds") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // print events to file + if flagWriteEventsToFile && flagLive { + err := fmt.Errorf("cannot write raw perf events to file when --%s is set", flagLiveName) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + // only one output format if live + if flagLive && len(flagOutputFormat) > 1 { + err := fmt.Errorf("specify one output format with --%s when --%s is set", flagOutputFormatName, flagLiveName) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + return nil +} + +type targetContext struct { + target target.Target + err error + perfPath string + tempDir string + metadata Metadata + nmiDisabled bool + perfMuxIntervals map[string]int + groupDefinitions []GroupDefinition + metricDefinitions []MetricDefinition + printedFiles []string +} + +type targetError struct { + target target.Target + err error +} + +func runCmd(cmd *cobra.Command, args []string) error { + // appContext is the application context that holds common data and resources. + appContext := cmd.Context().Value(common.AppContext{}).(common.AppContext) + localTempDir := appContext.TempDir + localOutputDir := appContext.OutputDir + + // handle signals + // child processes will exit when the signals are received which will + // allow this app to exit normally + sigChannel := make(chan os.Signal, 1) + signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-sigChannel + setSignalReceived() + slog.Info("received signal", slog.String("signal", sig.String())) + }() + + // round up to next perfPrintInterval second (the collection interval used by perf stat) + if flagDuration != 0 { + qf := float64(flagDuration) / float64(flagPerfPrintInterval) + qi := flagDuration / flagPerfPrintInterval + if qf > float64(qi) { + flagDuration = (qi + 1) * flagPerfPrintInterval + } + } + + // get the targets + myTargets, err := common.GetTargets(cmd, !flagNoRoot, !flagNoRoot, localTempDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + if len(myTargets) == 0 { + err := fmt.Errorf("no targets specified") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + targetTempRoot, _ := cmd.Flags().GetString(common.FlagTargetTempDirName) + + if flagLive && len(myTargets) > 1 { + err := fmt.Errorf("live mode is only supported for a single target") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + + // create progress spinner + multiSpinner := progress.NewMultiSpinner() + for _, myTarget := range myTargets { + err := multiSpinner.AddSpinner(myTarget.GetName()) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + } + multiSpinner.Start() + defer multiSpinner.Finish() + + // extract perf into local temp directory (assumes all targets have the same architecture) + localPerfPath, err := extractPerf(myTargets[0], localTempDir) + if err != nil { + err = fmt.Errorf("failed to extract perf: %w", err) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + cmd.SilenceUsage = true + return err + } + + // prepare the targets + channelTargetError := make(chan targetError) + var targetContexts []targetContext + for _, myTarget := range myTargets { + targetContexts = append(targetContexts, targetContext{target: myTarget}) + } + for i := range targetContexts { + go prepareTarget(&targetContexts[i], targetTempRoot, localTempDir, localPerfPath, channelTargetError, multiSpinner.Status) + } + for range targetContexts { + targetError := <-channelTargetError + if targetError.err != nil { + slog.Error("failed to prepare target", slog.String("target", targetError.target.GetName()), slog.String("error", targetError.err.Error())) + } + } + + // schedule temporary directory cleanup + if cmd.Parent().PersistentFlags().Lookup("debug").Value.String() != "true" { // don't remove the directory if we're debugging + defer func() { + for _, targetContext := range targetContexts { + if targetContext.tempDir != "" { + err := targetContext.target.RemoveDirectory(targetContext.tempDir) + if err != nil { + slog.Error("failed to remove temp directory", slog.String("directory", targetContext.tempDir), slog.String("error", err.Error())) + } + } + } + }() + } + + // schedule NMI watchdog reset + defer func() { + for _, targetContext := range targetContexts { + if targetContext.nmiDisabled { + err := EnableNMIWatchdog(targetContext.target, targetContext.tempDir) + if err != nil { + slog.Error("failed to re-enable NMI watchdog", slog.String("target", targetContext.target.GetName()), slog.String("error", err.Error())) + } + } + } + }() + + // schedule mux interval reset + defer func() { + for _, targetContext := range targetContexts { + err := SetMuxIntervals(targetContext.target, targetContext.perfMuxIntervals, localTempDir) + if err != nil { + slog.Error("failed to reset perf mux intervals", slog.String("target", targetContext.target.GetName()), slog.String("error", err.Error())) + } + } + }() + + // prepare the metrics for each target + for i := range targetContexts { + go prepareMetrics(&targetContexts[i], localTempDir, channelTargetError, multiSpinner.Status) + } + for range targetContexts { + targetError := <-channelTargetError + if targetError.err != nil { + slog.Error("failed to prepare metrics", slog.String("target", targetError.target.GetName()), slog.String("error", targetError.err.Error())) + } + } + + // show metric names and exit, if requested + if flagShowMetricNames { + // stop the multiSpinner + multiSpinner.Finish() + for _, targetContext := range targetContexts { + fmt.Printf("\nMetrics available on %s:\n", targetContext.target.GetName()) + for _, metric := range targetContext.metricDefinitions { + fmt.Printf("\"%s\"\n", metric.Name) + } + } + return nil + } + + // create the local output directory + if !flagLive { + err = common.CreateOutputDir(localOutputDir) + if err != nil { + err = fmt.Errorf("failed to create output directory: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + cmd.SilenceUsage = true + return err + } + } + + // write metadata to file + if flagWriteEventsToFile { + for _, targetContext := range targetContexts { + if err = targetContext.metadata.WriteJSONToFile(localOutputDir + "/" + targetContext.target.GetName() + "_" + "metadata.json"); err != nil { + err = fmt.Errorf("failed to write metadata to file: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + cmd.SilenceUsage = true + return err + } + } + } + + // start the metric collection + for i := range targetContexts { + if targetContexts[i].err == nil { + finalMessage := "collecting metrics" + if flagDuration == 0 { + finalMessage += ", press Ctrl+C to stop" + } else { + finalMessage += fmt.Sprintf(" for %d seconds", flagDuration) + } + multiSpinner.Status(targetContexts[i].target.GetName(), finalMessage) + } + go collectOnTarget(&targetContexts[i], localTempDir, localOutputDir, channelTargetError, multiSpinner.Status) + } + if flagLive { + multiSpinner.Finish() + } + for range targetContexts { + targetError := <-channelTargetError + if targetError.err != nil { + slog.Error("failed to collect on target", slog.String("target", targetError.target.GetName()), slog.String("error", targetError.err.Error())) + } + } + // finalize and stop the spinner + for _, targetContext := range targetContexts { + if targetContext.err == nil { + multiSpinner.Status(targetContext.target.GetName(), "collection complete") + } + } + // summarize outputs + if !flagLive { + multiSpinner.Finish() + for i := range targetContexts { + if targetContexts[i].err != nil { + continue + } + myTarget := targetContexts[i].target + // csv summary + out, err := Summarize(localOutputDir+"/"+myTarget.GetName()+"_"+"metrics.csv", false) + if err != nil { + err = fmt.Errorf("failed to summarize output: %w", err) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + if err = os.WriteFile(localOutputDir+"/"+myTarget.GetName()+"_"+"metrics_summary.csv", []byte(out), 0644); err != nil { + err = fmt.Errorf("failed to write summary to file: %w", err) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + targetContexts[i].printedFiles = append(targetContexts[i].printedFiles, localOutputDir+"/"+myTarget.GetName()+"_"+"metrics_summary.csv") + // html summary + htmlSummary := (flagScope == scopeSystem || flagScope == scopeProcess) && flagGranularity == granularitySystem + if htmlSummary { + out, err = Summarize(localOutputDir+"/"+myTarget.GetName()+"_"+"metrics.csv", true) + if err != nil { + err = fmt.Errorf("failed to summarize output as HTML: %w", err) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + if err = os.WriteFile(localOutputDir+"/"+myTarget.GetName()+"_"+"metrics_summary.html", []byte(out), 0644); err != nil { + err = fmt.Errorf("failed to write HTML summary to file: %w", err) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + cmd.SilenceUsage = true + return err + } + targetContexts[i].printedFiles = append(targetContexts[i].printedFiles, localOutputDir+"/"+myTarget.GetName()+"_"+"metrics_summary.html") + } + } + // print the names of the files that were created + fmt.Println() + fmt.Println("Metric files:") + for i := range targetContexts { + for _, file := range targetContexts[i].printedFiles { + fmt.Printf(" %s\n", file) + } + } + } + return nil +} + +func prepareTarget(targetContext *targetContext, targetTempRoot string, localTempDir string, localPerfPath string, channelError chan targetError, statusUpdate progress.MultiSpinnerUpdateFunc) { + myTarget := targetContext.target + // create a temporary directory on the target + statusUpdate(myTarget.GetName(), "configuring target") + var err error + if targetContext.tempDir, err = myTarget.CreateTempDirectory(targetTempRoot); err != nil { + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %v", err)) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + // check if NMI watchdog is enabled and disable it if necessary + if !flagNoRoot { + var nmiWatchdogEnabled bool + if nmiWatchdogEnabled, err = NMIWatchdogEnabled(myTarget); err != nil { + err = fmt.Errorf("failed to retrieve NMI watchdog status: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + if nmiWatchdogEnabled { + if err = DisableNMIWatchdog(myTarget, localTempDir); err != nil { + err = fmt.Errorf("failed to disable NMI watchdog: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + targetContext.nmiDisabled = true + } + } + // set perf mux interval to desired value + if !flagNoRoot { + if targetContext.perfMuxIntervals, err = GetMuxIntervals(myTarget, localTempDir); err != nil { + err = fmt.Errorf("failed to get perf mux intervals: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + if err = SetAllMuxIntervals(myTarget, flagPerfMuxInterval, localTempDir); err != nil { + err = fmt.Errorf("failed to set all perf mux intervals: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + } + // get the full path to the perf binary + if targetContext.perfPath, err = getPerfPath(myTarget, localPerfPath); err != nil { + err = fmt.Errorf("failed to find perf: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %v", err)) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + slog.Info("Using Linux perf", slog.String("target", targetContext.target.GetName()), slog.String("path", targetContext.perfPath)) + channelError <- targetError{target: myTarget, err: nil} +} + +func prepareMetrics(targetContext *targetContext, localTempDir string, channelError chan targetError, statusUpdate progress.MultiSpinnerUpdateFunc) { + myTarget := targetContext.target + if targetContext.err != nil { + channelError <- targetError{target: myTarget, err: nil} + return + } + // load metadata + statusUpdate(myTarget.GetName(), "collecting metadata") + var err error + if targetContext.metadata, err = LoadMetadata(myTarget, flagNoRoot, targetContext.perfPath, localTempDir); err != nil { + err = fmt.Errorf("failed to load metadata: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + slog.Info(targetContext.metadata.String()) + // load event definitions + var uncollectableEvents []string + if targetContext.groupDefinitions, uncollectableEvents, err = LoadEventGroups(flagEventFilePath, targetContext.metadata); err != nil { + err = fmt.Errorf("failed to load event definitions: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + // load metric definitions + if targetContext.metricDefinitions, err = LoadMetricDefinitions(flagMetricFilePath, flagMetricsList, uncollectableEvents, targetContext.metadata); err != nil { + err = fmt.Errorf("failed to load metric definitions: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + // configure metrics + if err = ConfigureMetrics(targetContext.metricDefinitions, GetEvaluatorFunctions(), targetContext.metadata); err != nil { + err = fmt.Errorf("failed to configure metrics: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + channelError <- targetError{target: myTarget, err: nil} +} + +func collectOnTarget(targetContext *targetContext, localTempDir string, localOutputDir string, channelError chan targetError, statusUpdate progress.MultiSpinnerUpdateFunc) { + myTarget := targetContext.target + if targetContext.err != nil { + channelError <- targetError{target: myTarget, err: nil} + return + } + // refresh if collecting per-process/cgroup and list of PIDs/CIDs not specified + refresh := (flagScope == scopeProcess && len(flagPidList) == 0) || + (flagScope == scopeCgroup && len(flagCidList) == 0) + errorChannel := make(chan error) + frameChannel := make(chan []MetricFrame) + printCompleteChannel := make(chan []string) + totalRuntimeSeconds := 0 // only relevant in process scope + go printMetrics(frameChannel, myTarget.GetName(), localOutputDir, printCompleteChannel) + var err error + for { + // get current time for use in setting timestamps on output + gCollectionStartTime = time.Now() + var perfCommand *exec.Cmd + var processes []Process + // get the perf command + if processes, perfCommand, err = getPerfCommand(myTarget, targetContext.perfPath, targetContext.groupDefinitions, localTempDir); err != nil { + err = fmt.Errorf("failed to get perf command: %w", err) + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + break + } + beginTimestamp := time.Now() + go runPerf(myTarget, flagNoRoot, processes, perfCommand, targetContext.groupDefinitions, targetContext.metricDefinitions, targetContext.metadata, localTempDir, localOutputDir, frameChannel, errorChannel) + // wait for runPerf to finish + perfErr := <-errorChannel // capture and return all errors + if perfErr != nil { + if !getSignalReceived() { + err = perfErr + statusUpdate(myTarget.GetName(), fmt.Sprintf("Error: %s", err.Error())) + } + break + } + // no perf errors, continue + endTimestamp := time.Now() + totalRuntimeSeconds += int(endTimestamp.Sub(beginTimestamp).Seconds()) + if !refresh || (flagDuration != 0 && totalRuntimeSeconds >= flagDuration) { + break + } + } + close(frameChannel) // we're done writing frames so shut it down + // wait for printing to complete + targetContext.printedFiles = <-printCompleteChannel + close(printCompleteChannel) + if err != nil { + targetContext.err = err + channelError <- targetError{target: myTarget, err: err} + return + } + channelError <- targetError{target: myTarget, err: nil} +} + +// printMetrics receives metric frames over the provided channel and prints them to file and stdout in the requested format. +// It exits when the channel is closed. +func printMetrics(frameChannel chan []MetricFrame, targetName string, outputDir string, doneChannel chan []string) { + var printedFiles []string + // block until next set of metric frames arrives, will exit loop when channel is closed + for metricFrames := range frameChannel { + fileName := printMetricsTxt(metricFrames, targetName, flagLive && flagOutputFormat[0] == formatTxt, !flagLive && util.StringInList(formatTxt, flagOutputFormat), outputDir) + if fileName != "" { + printedFiles = util.UniqueAppend(printedFiles, fileName) + } + fileName = printMetricsJSON(metricFrames, targetName, flagLive && flagOutputFormat[0] == formatJSON, !flagLive && util.StringInList(formatJSON, flagOutputFormat), outputDir) + if fileName != "" { + printedFiles = util.UniqueAppend(printedFiles, fileName) + } + // csv is always written to file unless no files are requested -- we need it to create the summary reports + fileName = printMetricsCSV(metricFrames, targetName, flagLive && flagOutputFormat[0] == formatCSV, !flagLive, outputDir) + if fileName != "" { + printedFiles = util.UniqueAppend(printedFiles, fileName) + } + fileName = printMetricsWide(metricFrames, targetName, flagLive && flagOutputFormat[0] == formatWide, !flagLive && util.StringInList(formatWide, flagOutputFormat), outputDir) + if fileName != "" { + printedFiles = util.UniqueAppend(printedFiles, fileName) + } + } + doneChannel <- printedFiles +} + +// extractPerf extracts the perf binary from the resources to the local temporary directory. +func extractPerf(myTarget target.Target, localTempDir string) (string, error) { + // get the target architecture + arch, err := myTarget.GetArchitecture() + if err != nil { + return "", fmt.Errorf("failed to get target architecture: %w", err) + } + // extract the perf binary + return util.ExtractResource(script.Resources, path.Join("resources", arch, "perf"), localTempDir) +} + +// getPerfPath returns the path to the perf binary, along with a temporary directory and any error encountered. +// If the target is local, the function returns the local path to the perf binary. +// If the target is remote, the function creates a temporary directory on the remote system, and pushes the perf binary to that directory. +func getPerfPath(myTarget target.Target, localPerfPath string) (perfPath string, err error) { + if _, ok := myTarget.(*target.LocalTarget); ok { + perfPath = localPerfPath + } else { + targetTempDir := myTarget.GetTempDirectory() + if targetTempDir == "" { + panic("targetTempDir is empty") + } + if err = myTarget.PushFile(localPerfPath, targetTempDir); err != nil { + slog.Error("failed to push perf binary to remote directory", slog.String("error", err.Error())) + return + } + perfPath = path.Join(targetTempDir, "perf") + } + return +} + +// getPerfCommandArgs returns the command arguments for the 'perf stat' command +// based on the provided parameters. +// +// Parameters: +// - pids: The process IDs for which to collect performance data. If flagScope is +// set to "process", the data will be collected only for these processes. +// - cgroups: The list of cgroups for which to collect performance data. If +// flagScope is set to "cgroup", the data will be collected only for these cgroups. +// - timeout: The timeout value in seconds. If flagScope is not set to "cgroup" +// and timeout is not 0, the 'sleep' command will be added to the arguments +// with the specified timeout value. +// - eventGroups: The list of event groups to collect. Each event group is a +// collection of events to be monitored. +// +// Returns: +// - args: The command arguments for the 'perf stat' command. +// - err: An error, if any. +func getPerfCommandArgs(pids string, cgroups []string, timeout int, eventGroups []GroupDefinition) (args []string, err error) { + // -I: print interval in ms + // -j: json formatted event output + args = append(args, "stat", "-I", fmt.Sprintf("%d", flagPerfPrintInterval*1000), "-j") + if flagScope == scopeSystem { + args = append(args, "-a") // system-wide collection + if flagGranularity == granularityCPU || flagGranularity == granularitySocket { + args = append(args, "-A") // no aggregation + } + } else if flagScope == scopeProcess { + args = append(args, "-p", pids) // collect only for these processes + } else if flagScope == scopeCgroup { + args = append(args, "--for-each-cgroup", strings.Join(cgroups, ",")) // collect only for these cgroups + } + // -e: event groups to collect + args = append(args, "-e") + var groups []string + for _, group := range eventGroups { + var events []string + for _, event := range group { + events = append(events, event.Raw) + } + groups = append(groups, fmt.Sprintf("{%s}", strings.Join(events, ","))) + } + args = append(args, fmt.Sprintf("'%s'", strings.Join(groups, ","))) + if len(argsApplication) > 0 { + // add application args + args = append(args, "--") + args = append(args, argsApplication...) + } else if flagScope != scopeCgroup && timeout != 0 { + // add timeout + args = append(args, "sleep", fmt.Sprintf("%d", timeout)) + } + return +} + +// getPerfCommand is responsible for assembling the command that will be +// executed to collect event data +func getPerfCommand(myTarget target.Target, perfPath string, eventGroups []GroupDefinition, localTempDir string) (processes []Process, perfCommand *exec.Cmd, err error) { + if flagScope == scopeSystem { + var args []string + if args, err = getPerfCommandArgs("", []string{}, flagDuration, eventGroups); err != nil { + err = fmt.Errorf("failed to assemble perf args: %v", err) + return + } + perfCommand = exec.Command(perfPath, args...) + } else if flagScope == scopeProcess { + if len(flagPidList) > 0 { + if processes, err = GetProcesses(myTarget, flagPidList); err != nil { + return + } + if len(processes) == 0 { + err = fmt.Errorf("failed to find processes associated with designated PIDs: %v", flagPidList) + return + } + } else { + if processes, err = GetHotProcesses(myTarget, flagCount, flagFilter); err != nil { + return + } + if len(processes) == 0 { + if flagFilter == "" { + err = fmt.Errorf("failed to find \"hot\" processes") + return + } else { + err = fmt.Errorf("failed to find processes matching filter: %s", flagFilter) + return + } + } + } + var timeout int + if flagDuration > 0 { + timeout = flagDuration + } else if len(flagPidList) == 0 { // don't refresh if PIDs are specified + timeout = flagRefresh // refresh hot processes every flagRefresh seconds + } + pidList := make([]string, 0, len(processes)) + for _, process := range processes { + pidList = append(pidList, process.pid) + } + var args []string + if args, err = getPerfCommandArgs(strings.Join(pidList, ","), []string{}, timeout, eventGroups); err != nil { + err = fmt.Errorf("failed to assemble perf args: %v", err) + return + } + perfCommand = exec.Command(perfPath, args...) + } else if flagScope == scopeCgroup { + var cgroups []string + if len(flagCidList) > 0 { + if cgroups, err = GetCgroups(myTarget, flagCidList, localTempDir); err != nil { + return + } + } else { + if cgroups, err = GetHotCgroups(myTarget, flagCount, flagFilter, localTempDir); err != nil { + return + } + } + if len(cgroups) == 0 { + err = fmt.Errorf("no CIDs selected") + return + } + var args []string + if args, err = getPerfCommandArgs("", cgroups, -1, eventGroups); err != nil { + err = fmt.Errorf("failed to assemble perf args: %v", err) + return + } + perfCommand = exec.Command(perfPath, args...) + } + return +} + +// runPerf starts Linux perf using the provided command, then reads perf's output +// until perf stops. When collecting for cgroups, perf will be manually terminated if/when the +// run duration exceeds the collection time or the time when the cgroup list needs +// to be refreshed. +func runPerf(myTarget target.Target, noRoot bool, processes []Process, cmd *exec.Cmd, eventGroupDefinitions []GroupDefinition, metricDefinitions []MetricDefinition, metadata Metadata, localTempDir string, outputDir string, frameChannel chan []MetricFrame, errorChannel chan error) { + var err error + defer func() { errorChannel <- err }() + cpuCount := metadata.SocketCount * metadata.CoresPerSocket * metadata.ThreadsPerCore + outputLines := make([][]byte, 0, cpuCount*150) // a rough approximation of expected number of events + // start perf + perfCommand := strings.Join(cmd.Args, " ") + stdoutChannel := make(chan string) + stderrChannel := make(chan string) + exitcodeChannel := make(chan int) + scriptErrorChannel := make(chan error) + cmdChannel := make(chan *exec.Cmd) + slog.Debug("running perf stat", slog.String("command", perfCommand)) + go script.RunScriptAsync(myTarget, script.ScriptDefinition{Name: "perf stat", Script: perfCommand, Superuser: !noRoot}, localTempDir, stdoutChannel, stderrChannel, exitcodeChannel, scriptErrorChannel, cmdChannel) + var localCommand *exec.Cmd + select { + case cmd := <-cmdChannel: + localCommand = cmd + case err := <-scriptErrorChannel: + if err != nil { + return + } + } + // must manually terminate perf in cgroup scope when a timeout is specified and/or need to refresh cgroups + startPerfTimestamp := time.Now() + var timeout int + if flagScope == scopeCgroup && (flagDuration != 0 || len(flagCidList) == 0) { + if flagDuration > 0 && flagDuration < flagRefresh { + timeout = flagDuration + } else { + timeout = flagRefresh + } + } + // Use a timer to determine when we received an entire frame of events from perf + // The timer will expire when no lines (events) have been received from perf for more than 100ms. This + // works because perf writes the events to stderr in a burst every collection interval, e.g., 5 seconds. + // When the timer expires, this code assumes that perf is done writing events to stderr. + // The first duration needs to be longer than the time it takes for perf to print its first line of output. + t1 := time.NewTimer(time.Duration(2 * flagPerfPrintInterval * 1000)) + var frameTimestamp float64 + frameCount := 0 + stopAnonymousFuncChannel := make(chan bool) + go func() { + for { + select { + case <-t1.C: // waits for timer to expire + case <-stopAnonymousFuncChannel: // wait for signal to exit the goroutine + return + } + if len(outputLines) != 0 { + if flagWriteEventsToFile { + if err = writeEventsToFile(outputDir+"/"+myTarget.GetName()+"_"+"events.json", outputLines); err != nil { + err = fmt.Errorf("failed to write events to raw file: %v", err) + slog.Error(err.Error()) + return + } + } + var metricFrames []MetricFrame + if metricFrames, frameTimestamp, err = ProcessEvents(outputLines, eventGroupDefinitions, metricDefinitions, processes, frameTimestamp, metadata, outputDir); err != nil { + slog.Warn(err.Error()) + outputLines = [][]byte{} // empty it + continue + } + for i := range metricFrames { + frameCount += 1 + metricFrames[i].FrameCount = frameCount + } + frameChannel <- metricFrames + outputLines = [][]byte{} // empty it + } + if timeout != 0 && int(time.Since(startPerfTimestamp).Seconds()) > timeout { + localCommand.Process.Signal(os.Interrupt) + } + } + }() + // read perf output + done := false + for !done { + select { + case err := <-scriptErrorChannel: + if err != nil { + slog.Error("error from perf", slog.String("error", err.Error())) + } + done = true + case exitCode := <-exitcodeChannel: + slog.Debug("perf exited", slog.Int("exit code", exitCode)) + done = true + case line := <-stderrChannel: + t1.Stop() + t1.Reset(100 * time.Millisecond) // 100ms is somewhat arbitrary, but seems to work + outputLines = append(outputLines, []byte(line)) + } + } + t1.Stop() + // send signal to exit the goroutine + defer func() { stopAnonymousFuncChannel <- true }() + // process any remaining events + if len(outputLines) != 0 { + if flagWriteEventsToFile { + if err = writeEventsToFile(outputDir+"/"+myTarget.GetName()+"_"+"events.json", outputLines); err != nil { + err = fmt.Errorf("failed to write events to raw file: %v", err) + slog.Error(err.Error()) + return + } + } + var metricFrames []MetricFrame + if metricFrames, frameTimestamp, err = ProcessEvents(outputLines, eventGroupDefinitions, metricDefinitions, processes, frameTimestamp, metadata, outputDir); err != nil { + slog.Error(err.Error()) + return + } + for i := range metricFrames { + frameCount += 1 + metricFrames[i].FrameCount = frameCount + } + frameChannel <- metricFrames + } +} diff --git a/cmd/metrics/nmi_watchdog.go b/cmd/metrics/nmi_watchdog.go new file mode 100644 index 0000000..b095a10 --- /dev/null +++ b/cmd/metrics/nmi_watchdog.go @@ -0,0 +1,106 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// nmi_watchdog provides helper functions for enabling and disabling the NMI (non-maskable interrupt) watchdog + +import ( + "fmt" + "log/slog" + "os/exec" + "strings" + + "perfspect/internal/script" + "perfspect/internal/target" +) + +// EnableNMIWatchdog - sets the kernel.nmi_watchdog value to "1" +func EnableNMIWatchdog(myTarget target.Target, localTempDir string) (err error) { + slog.Info("enabling NMI watchdog") + err = setNMIWatchdog(myTarget, "1", localTempDir) + return +} + +// DisableNMIWatchdog - sets the kernel.nmi_watchdog value to "0" +func DisableNMIWatchdog(myTarget target.Target, localTempDir string) (err error) { + slog.Info("disabling NMI watchdog") + err = setNMIWatchdog(myTarget, "0", localTempDir) + return +} + +// NMIWatchdogEnabled - reads the kernel.nmi_watchdog value. If it is "1", returns true +func NMIWatchdogEnabled(myTarget target.Target) (enabled bool, err error) { + var setting string + if setting, err = getNMIWatchdog(myTarget); err != nil { + return + } + enabled = setting == "1" + return +} + +// getNMIWatchdog - gets the kernel.nmi_watchdog configuration value (0 or 1) +func getNMIWatchdog(myTarget target.Target) (setting string, err error) { + // sysctl kernel.nmi_watchdog + // kernel.nmi_watchdog = [0|1] + var sysctl string + if sysctl, err = findSysctl(myTarget); err != nil { + return + } + cmd := exec.Command(sysctl, "kernel.nmi_watchdog") + stdout, _, _, err := myTarget.RunCommand(cmd, 0) + if err != nil { + return + } + out := stdout + setting = out[len(out)-2 : len(out)-1] + return +} + +// setNMIWatchdog -sets the kernel.nmi_watchdog configuration value +func setNMIWatchdog(myTarget target.Target, setting string, localTempDir string) (err error) { + // sysctl kernel.nmi_watchdog=[0|1] + var sysctl string + if sysctl, err = findSysctl(myTarget); err != nil { + return + } + _, err = script.RunScript(myTarget, script.ScriptDefinition{ + Name: "set NMI watchdog", + Script: fmt.Sprintf("%s kernel.nmi_watchdog=%s", sysctl, setting), + Superuser: true}, + localTempDir) + if err != nil { + err = fmt.Errorf("failed to set NMI watchdog to %s, %v", setting, err) + return + } + var outSetting string + if outSetting, err = getNMIWatchdog(myTarget); err != nil { + return + } + if outSetting != setting { + err = fmt.Errorf("failed to set NMI watchdog to %s", setting) + } + return +} + +// findSysctl - gets a useable path to sysctl or error +func findSysctl(myTarget target.Target) (path string, err error) { + cmd := exec.Command("which", "sysctl") + stdout, _, _, err := myTarget.RunCommand(cmd, 0) + if err == nil { + //found it + path = strings.TrimSpace(stdout) + return + } + // didn't find it on the path, try being specific + sbinPath := "/usr/sbin/sysctl" + cmd = exec.Command("which", sbinPath) + _, _, _, err = myTarget.RunCommand(cmd, 0) + if err == nil { + // found it + path = sbinPath + return + } + err = fmt.Errorf("sysctl not found on path or at %s", sbinPath) + return +} diff --git a/cmd/metrics/perf_mux.go b/cmd/metrics/perf_mux.go new file mode 100644 index 0000000..61e5fb6 --- /dev/null +++ b/cmd/metrics/perf_mux.go @@ -0,0 +1,58 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// Linux perf event/group multiplexing interval helper functions + +import ( + "fmt" + "strconv" + "strings" + + "perfspect/internal/script" + "perfspect/internal/target" +) + +// GetMuxIntervals - get a map of sysfs device file names to current mux value for the associated device +func GetMuxIntervals(myTarget target.Target, localTempDir string) (intervals map[string]int, err error) { + bash := "for file in $(find /sys/devices -type f -name perf_event_mux_interval_ms); do echo $file $(cat $file); done" + scriptOutput, err := script.RunScript(myTarget, script.ScriptDefinition{Name: "get mux intervals", Script: bash, Superuser: false}, localTempDir) + if err != nil { + return + } + intervals = make(map[string]int) + for _, line := range strings.Split(scriptOutput.Stdout, "\n") { + fields := strings.Fields(line) + if len(fields) == 2 { + if interval, err := strconv.Atoi(fields[1]); err == nil { + intervals[fields[0]] = interval + } + } + } + return +} + +// SetMuxIntervals - write the given intervals (values in ms) to the given sysfs device file names (key) +func SetMuxIntervals(myTarget target.Target, intervals map[string]int, localTempDir string) (err error) { + var bash string + for device := range intervals { + bash += fmt.Sprintf("echo %d > %s; ", intervals[device], device) + } + scriptOutput, err := script.RunScript(myTarget, script.ScriptDefinition{Name: "set mux intervals", Script: bash, Superuser: true}, localTempDir) + if err != nil { + err = fmt.Errorf("failed to set mux interval on device: %s, %d, %v", scriptOutput.Stderr, scriptOutput.Exitcode, err) + return + } + return +} + +// SetAllMuxIntervals - writes the given interval (ms) to all perf mux sysfs device files +func SetAllMuxIntervals(myTarget target.Target, interval int, localTempDir string) (err error) { + bash := fmt.Sprintf("for file in $(find /sys/devices -type f -name perf_event_mux_interval_ms); do echo %d > $file; done", interval) + _, err = script.RunScript(myTarget, script.ScriptDefinition{Name: "set all mux intervals", Script: bash, Superuser: true}, localTempDir) + if err != nil { + return + } + return +} diff --git a/cmd/metrics/print.go b/cmd/metrics/print.go new file mode 100644 index 0000000..0a9acca --- /dev/null +++ b/cmd/metrics/print.go @@ -0,0 +1,270 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "encoding/json" + "fmt" + "log/slog" + "math" + "os" + "strconv" + "strings" + "time" +) + +func printMetricsJSON(metricFrames []MetricFrame, targetName string, printToStdout bool, printToFile bool, outputDir string) string { + if !printToStdout && !printToFile { + return "" + } + for _, metricFrame := range metricFrames { + // can't Marshal NaN or Inf values in JSON, so no need to set them to a specific value + filteredMetricFrame := metricFrame + filteredMetricFrame.Metrics = make([]Metric, 0, len(metricFrame.Metrics)) + for _, metric := range metricFrame.Metrics { + if math.IsNaN(metric.Value) || math.IsInf(metric.Value, 0) { + filteredMetricFrame.Metrics = append(filteredMetricFrame.Metrics, Metric{Name: metric.Name, Value: -1}) + } else { + filteredMetricFrame.Metrics = append(filteredMetricFrame.Metrics, metric) + } + } + jsonBytes, err := json.Marshal(filteredMetricFrame) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + return "" + } + if printToStdout { + fmt.Println(string(jsonBytes)) + } + if printToFile { + file, err := os.OpenFile(outputDir+"/"+targetName+"_"+"metrics.json", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + return "" + } + defer file.Close() + file.WriteString(string(jsonBytes) + "\n") + return file.Name() + } + } + return "" +} + +func printMetricsCSV(metricFrames []MetricFrame, targetName string, printToStdout bool, printToFile bool, outputDir string) string { + if !printToStdout && !printToFile { + return "" + } + var file *os.File + if printToFile { + // open file for writing/appending + var err error + file, err = os.OpenFile(outputDir+"/"+targetName+"_"+"metrics.csv", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + return "" + } + defer file.Close() + } + for _, metricFrame := range metricFrames { + if metricFrame.FrameCount == 1 { + contextHeaders := "TS,SKT,CPU,CID," + if printToStdout { + fmt.Print(contextHeaders) + } + if printToFile { + file.WriteString(contextHeaders) + } + names := make([]string, 0, len(metricFrame.Metrics)) + for _, metric := range metricFrame.Metrics { + names = append(names, metric.Name) + } + metricNames := strings.Join(names, ",") + if printToStdout { + fmt.Println(metricNames) + } + if printToFile { + file.WriteString(metricNames + "\n") + } + } + metricContext := fmt.Sprintf("%d,%s,%s,%s,", gCollectionStartTime.Unix()+int64(metricFrame.Timestamp), metricFrame.Socket, metricFrame.CPU, metricFrame.Cgroup) + values := make([]string, 0, len(metricFrame.Metrics)) + for _, metric := range metricFrame.Metrics { + values = append(values, strconv.FormatFloat(metric.Value, 'g', 8, 64)) + } + metricValues := strings.ReplaceAll(strings.Join(values, ","), "NaN", "") + if printToStdout { + fmt.Println(metricContext + metricValues) + } + if printToFile { + file.WriteString(metricContext + metricValues + "\n") + return file.Name() + } + } + return "" +} + +func printMetricsWide(metricFrames []MetricFrame, targetName string, printToStdout bool, printToFile bool, outputDir string) string { + if !printToStdout && !printToFile { + return "" + } + var file *os.File + if printToFile { + // open file for writing/appending + var err error + file, err = os.OpenFile(outputDir+"/"+targetName+"_"+"metrics_wide.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + return "" + } + defer file.Close() + } + for _, metricFrame := range metricFrames { + var names []string + var values []float64 + for _, metric := range metricFrame.Metrics { + names = append(names, metric.Name) + values = append(values, metric.Value) + } + minColWidth := 6 + colSpacing := 3 + if metricFrame.FrameCount == 1 { // print headers + header := "Timestamp " // 10 + 3 + if metricFrame.PID != "" { + header += "PID " // 7 + 3 + header += "Command " // 15 + 3 + } else if metricFrame.Cgroup != "" { + header += "CID " + } + if metricFrame.CPU != "" { + header += "CPU " // 3 + 3 + } else if metricFrame.Socket != "" { + header += "SKT " // 3 + 3 + } + for _, name := range names { + extend := 0 + if len(name) < minColWidth { + extend = minColWidth - len(name) + } + header += fmt.Sprintf("%s%*s%*s", name, extend, "", colSpacing, "") + } + if printToStdout { + fmt.Println(header) + } + if printToFile { + file.WriteString(header + "\n") + } + } + // handle values + TimestampColWidth := 10 + formattedTimestamp := fmt.Sprintf("%d", gCollectionStartTime.Unix()+int64(metricFrame.Timestamp)) + row := fmt.Sprintf("%s%*s%*s", formattedTimestamp, TimestampColWidth-len(formattedTimestamp), "", colSpacing, "") + if metricFrame.PID != "" { + PIDColWidth := 7 + commandColWidth := 15 + row += fmt.Sprintf("%s%*s%*s", metricFrame.PID, PIDColWidth-len(metricFrame.PID), "", colSpacing, "") + var command string + if len(metricFrame.Cmd) <= commandColWidth { + command = metricFrame.Cmd + } else { + command = metricFrame.Cmd[:commandColWidth] + } + row += fmt.Sprintf("%s%*s%*s", command, commandColWidth-len(command), "", colSpacing, "") + } else if metricFrame.Cgroup != "" { + CIDColWidth := 7 + row += fmt.Sprintf("%s%*s%*s", metricFrame.Cgroup, CIDColWidth-len(metricFrame.Cgroup), "", colSpacing, "") + } + if metricFrame.CPU != "" { + CPUColWidth := 3 + row += fmt.Sprintf("%s%*s%*s", metricFrame.CPU, CPUColWidth-len(metricFrame.CPU), "", colSpacing, "") + } else if metricFrame.Socket != "" { + SKTColWidth := 3 + row += fmt.Sprintf("%s%*s%*s", metricFrame.Socket, SKTColWidth-len(metricFrame.Socket), "", colSpacing, "") + } + // handle the metric values + for i, value := range values { + colWidth := max(len(names[i]), minColWidth) + formattedVal := fmt.Sprintf("%.2f", value) + row += fmt.Sprintf("%s%*s%*s", formattedVal, colWidth-len(formattedVal), "", colSpacing, "") + } + if printToStdout { + fmt.Println(row) + } + if printToFile { + file.WriteString(row + "\n") + return file.Name() + } + } + return "" +} + +func printMetricsTxt(metricFrames []MetricFrame, targetName string, printToStdout bool, printToFile bool, outputDir string) string { + if !printToStdout && !printToFile { + return "" + } + var outputLines []string + if len(metricFrames) > 0 && metricFrames[0].Socket != "" { + outputLines = append(outputLines, "--------------------------------------------------------------------------------------") + outputLines = append(outputLines, fmt.Sprintf("- Metrics captured at %s", gCollectionStartTime.Add(time.Second*time.Duration(int(metricFrames[0].Timestamp))).UTC())) + outputLines = append(outputLines, "--------------------------------------------------------------------------------------") + line := fmt.Sprintf("%-70s ", "metric") + for i := range len(metricFrames) { + line += fmt.Sprintf("%15s", fmt.Sprintf("skt %s val", metricFrames[i].Socket)) + } + outputLines = append(outputLines, line) + line = fmt.Sprintf("%-70s ", "------------------------") + for range len(metricFrames) { + line += fmt.Sprintf("%15s", "----------") + } + outputLines = append(outputLines, line) + for i := range metricFrames[0].Metrics { + line = fmt.Sprintf("%-70s ", metricFrames[0].Metrics[i].Name) + for _, metricFrame := range metricFrames { + line += fmt.Sprintf("%15s", strconv.FormatFloat(metricFrame.Metrics[i].Value, 'g', 4, 64)) + } + outputLines = append(outputLines, line) + } + } else { + for _, metricFrame := range metricFrames { + outputLines = append(outputLines, "--------------------------------------------------------------------------------------") + outputLines = append(outputLines, fmt.Sprintf("- Metrics captured at %s", gCollectionStartTime.Add(time.Second*time.Duration(int(metricFrame.Timestamp))).UTC())) + if metricFrame.PID != "" { + outputLines = append(outputLines, fmt.Sprintf("- PID: %s", metricFrame.PID)) + outputLines = append(outputLines, fmt.Sprintf("- CMD: %s", metricFrame.Cmd)) + } else if metricFrame.Cgroup != "" { + outputLines = append(outputLines, fmt.Sprintf("- CID: %s", metricFrame.Cgroup)) + } + if metricFrame.CPU != "" { + outputLines = append(outputLines, fmt.Sprintf("- CPU: %s", metricFrame.CPU)) + } else if metricFrame.Socket != "" { + outputLines = append(outputLines, fmt.Sprintf("- Socket: %s", metricFrame.Socket)) // TODO: remove this, it shouldn't happen + } + outputLines = append(outputLines, "--------------------------------------------------------------------------------------") + outputLines = append(outputLines, fmt.Sprintf("%-70s %15s", "metric", "value")) + outputLines = append(outputLines, fmt.Sprintf("%-70s %15s", "------------------------", "----------")) + for _, metric := range metricFrame.Metrics { + outputLines = append(outputLines, fmt.Sprintf("%-70s %15s", metric.Name, strconv.FormatFloat(metric.Value, 'g', 4, 64))) + } + } + } + if printToStdout { + fmt.Println(strings.Join(outputLines, "\n")) + } + if printToFile { + // open file for writing/appending + file, err := os.OpenFile(outputDir+"/"+targetName+"_"+"metrics.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + return "" + } + defer file.Close() + file.WriteString(strings.Join(outputLines, "\n") + "\n") + return file.Name() + } + return "" +} diff --git a/cmd/metrics/process.go b/cmd/metrics/process.go new file mode 100644 index 0000000..8f718c2 --- /dev/null +++ b/cmd/metrics/process.go @@ -0,0 +1,225 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// Linux process information helper functions + +import ( + "fmt" + "log/slog" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "perfspect/internal/common" + "perfspect/internal/script" + "perfspect/internal/target" +) + +type Process struct { + pid string + ppid string + comm string + cmd string +} + +// pid,ppid,comm,cmd +var psRegex = `^\s*(\d+)\s+(\d+)\s+([\w\d\(\)\:\/_\-\:\.]+)\s+(.*)` + +// GetProcesses - gets the list of processes associated with the given list of +// process IDs. An error occurs when a given PID is not found in the current +// set of running processes. +func GetProcesses(myTarget target.Target, pids []string) (processes []Process, err error) { + for _, pid := range pids { + if processExists(myTarget, pid) { + var process Process + if process, err = getProcess(myTarget, pid); err != nil { + return + } + processes = append(processes, process) + } + } + return +} + +// GetCgroups - gets the list of full cgroup names associated with the given list of +// partial cgroup names. An error occurs when a given cgroup name is not found in the +// current set of process cgroups. +func GetCgroups(myTarget target.Target, cids []string, localTempDir string) (cgroups []string, err error) { + for _, cid := range cids { + var cgroup string + if cgroup, err = getCgroup(myTarget, cid, localTempDir); err != nil { + return + } + cgroups = append(cgroups, cgroup) + } + return +} + +// GetHotProcesses - get maxProcesses processes with highest CPU utilization, matching +// filter if provided +func GetHotProcesses(myTarget target.Target, maxProcesses int, filter string) (processes []Process, err error) { + // run ps to get list of processes sorted by cpu utilization (descending) + cmd := exec.Command("ps", "-a", "-x", "-h", "-o", "pid,ppid,comm,cmd", "--sort=-%cpu") + stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0) + if err != nil { + err = fmt.Errorf("failed to get hot processes: %s, %d, %v", stderr, exitcode, err) + return + } + psOutput := stdout + var reFilter *regexp.Regexp + if filter != "" { + if reFilter, err = regexp.Compile(filter); err != nil { + return + } + } + reProcess := regexp.MustCompile(psRegex) + for _, line := range strings.Split(psOutput, "\n") { + if line == "" { + continue + } + match := reProcess.FindStringSubmatch(line) + if match == nil { + slog.Warn("Unrecognized ps output format", slog.String("line", line)) + continue + } + pid := match[1] + ppid := match[2] + comm := match[3] + cmd := match[4] + // skip processes that match the name of this program + if strings.Contains(cmd, filepath.Base(common.AppName)) { + slog.Debug("Skipping self", slog.String("PID", pid)) + continue + } + // skip processes that match the 'ps' command we ran above + if strings.Contains(cmd, "ps -a -x -h -o pid,ppid,comm,cmd --sort=-%cpu") { + slog.Debug("Skipping ps command", slog.String("PID", pid)) + continue + } + // if a filter was provided, skip processes that don't match + if reFilter != nil && !reFilter.MatchString(cmd) { + slog.Debug("Skipping process that doesn't match filter", slog.String("PID", pid), slog.String("Command", cmd)) + continue + } + processes = append(processes, Process{pid: pid, ppid: ppid, comm: comm, cmd: cmd}) + if len(processes) == maxProcesses { + break + } + } + var pids []string + for _, process := range processes { + pids = append(pids, process.pid) + } + slog.Debug("Hot PIDs", slog.String("PIDs", strings.Join(pids, ", "))) + return +} + +// GetHotCgroups - get maxCgroups cgroup names whose associated processes have the +// highest CPU utilization, matching filter if provided +func GetHotCgroups(myTarget target.Target, maxCgroups int, filter string, localTempDir string) (cgroups []string, err error) { + hotCgroupsScript := script.ScriptDefinition{ + Name: "hot_cgroups", + Script: fmt.Sprintf(` +# Directory to search for cgroups +search_dir="/sys/fs/cgroup" + +# Find matching cgroups +matching_cgroups=$(find "$search_dir" -type d \( -name "docker*scope" -o -name "containerd*scope" \)) + +# Filter matching cgroups based on regex if provided +regex=%s +if [ -n "$regex" ]; then + matching_cgroups=$(echo "$matching_cgroups" | grep -E "$regex") +fi + +# Get CPU usage for each matching cgroup +declare -A cgroup_cpu_usage +for cgroup in $matching_cgroups; do + if [ -f "$cgroup/cpu.stat" ]; then + cpu_usage=$(grep 'usage_usec' "$cgroup/cpu.stat" | awk '{print $2}') + if [ -n "$cpu_usage" ]; then + cgroup_path=${cgroup#"$search_dir"} + cgroup_cpu_usage["$cgroup_path"]=$cpu_usage + fi + fi +done + +# Sort cgroups by CPU usage and get the top N +for cgroup in "${!cgroup_cpu_usage[@]}"; do + echo "${cgroup_cpu_usage[$cgroup]} $cgroup" +done | sort -nr | head -n %d +`, filter, maxCgroups), + Superuser: true, + } + output, err := script.RunScript(myTarget, hotCgroupsScript, localTempDir) + if err != nil { + err = fmt.Errorf("failed to get hot cgroups: %v", err) + return + } + lines := strings.Split(output.Stdout, "\n") + for _, line := range lines { + if line == "" { + continue + } + fields := strings.Fields(line) + cgroups = append(cgroups, fields[1]) + } + slog.Debug("Hot CIDs", slog.String("CIDs", strings.Join(cgroups, ", "))) + return +} + +func processExists(myTarget target.Target, pid string) (exists bool) { + cmd := exec.Command("ps", "-p", pid) + _, _, _, err := myTarget.RunCommand(cmd, 0) + if err != nil { + exists = false + return + } + exists = true + return +} + +func getProcess(myTarget target.Target, pid string) (process Process, err error) { + cmd := exec.Command("ps", "-q", pid, "h", "-o", "pid,ppid,comm,cmd", "ww") + stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0) + if err != nil { + err = fmt.Errorf("failed to get process: %s, %d, %v", stderr, exitcode, err) + return + } + psOutput := stdout + reProcess := regexp.MustCompile(psRegex) + match := reProcess.FindStringSubmatch(psOutput) + if match == nil { + err = fmt.Errorf("Process not found, PID: %s, ps output: %s", pid, psOutput) + return + } + process = Process{pid: match[1], ppid: match[2], comm: match[3], cmd: match[4]} + return +} + +func getCgroup(myTarget target.Target, cid string, localTempDir string) (cGroupName string, err error) { + cgroupScript := script.ScriptDefinition{ + Name: "cgroup", + Script: fmt.Sprintf(` +# Directory to search for cgroups +search_dir="/sys/fs/cgroup" + +# Find the full cgroup path that matches the partial container ID +full_path=$(find "$search_dir" -type d | grep %s) + +cgroup_path=${full_path#"$search_dir"} +echo $cgroup_path +`, cid), + Superuser: true, + } + output, err := script.RunScript(myTarget, cgroupScript, localTempDir) + if err != nil { + err = fmt.Errorf("failed to get cgroup: %v", err) + return + } + cGroupName = strings.TrimSpace(output.Stdout) + return +} diff --git a/cmd/metrics/resources/base.html b/cmd/metrics/resources/base.html new file mode 100644 index 0000000..0aad713 --- /dev/null +++ b/cmd/metrics/resources/base.html @@ -0,0 +1,678 @@ + + + + + + PerfSpect + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/cmd/metrics/resources/events/x86_64/AuthenticAMD/bergamo.txt b/cmd/metrics/resources/events/x86_64/AuthenticAMD/bergamo.txt new file mode 100644 index 0000000..f6a25ec --- /dev/null +++ b/cmd/metrics/resources/events/x86_64/AuthenticAMD/bergamo.txt @@ -0,0 +1,445 @@ +# Bergamo event list + +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc:k'/k, +cpu-cycles, +cpu-cycles:k, +instructions, +instructions:k; + +# Branch Misprediction Ratio +cpu/event=0xc3,name='ex_ret_brn_misp'/, +cpu/event=0xc2,name='ex_ret_brn'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All Data Cache Accesses +cpu/event=0x29,umask=0x7,name='ls_dispatch.any'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L2 Cache Accesses +cpu/event=0x60,umask=0xf9,name='l2_request_g1.all_no_prefetch'/, +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu-cycles, +instructions; + +# L2 Cache Accesses from L1 Instruction Cache Misses +cpu/event=0x60,umask=0x10,name='l2_request_g1.cacheable_ic_read'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Accesses from L1 Data Cache Misses +cpu/event=0x60,umask=0xe8,name='l2_request_g1.all_dc'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Accesses from L2 Cache Hardware Prefetches +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L2 Cache Misses +cpu/event=0x64,umask=0x9,name='l2_cache_req_stat.ic_dc_miss_in_l2'/, +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Misses from L1 Instruction Cache Misses +cpu/event=0x64,umask=0x1,name='l2_cache_req_stat.ic_fill_miss'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Misses from L1 Data Cache Misses +cpu/event=0x64,umask=0x8,name='l2_cache_req_stat.ls_rd_blk_c'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Misses from L2 Cache Hardware Prefetches +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L2 Cache Hits +cpu/event=0x64,umask=0xf6,name='l2_cache_req_stat.ic_dc_hit_in_l2'/, +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Hits from L1 Instruction Cache Misses +cpu/event=0x64,umask=0x06,name='l2_cache_req_stat.ic_hit_in_l2'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Hits from L1 Data Cache Misses +cpu/event=0x64,umask=0xf0,name='l2_cache_req_stat.dc_hit_in_l2'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Hits from L2 Cache Hardware Prefetches +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L3 Cache Accesses +l3/event=0x4,umask=0xff,name='l3_lookup_state.all_coherent_accesses_to_l3'/; + +# L3 Cache Misses +l3/event=0x4,umask=0x1,name='l3_lookup_state.l3_miss'/; + +# L3 Cache Hits +l3/event=0x4,umask=0xfe,name='l3_lookup_state.l3_hit'/; + +# Average L3 Cache Read Miss Latency (ns) +l3/event=0xac,umask=0x3f,enallcores=0x1,enallslices=0x1,sliceid=0x3,threadmask=0x3,name='l3_xi_sampled_latency.all'/, +l3/event=0xad,umask=0x3f,enallcores=0x1,enallslices=0x1,sliceid=0x3,threadmask=0x3,name='l3_xi_sampled_latency_requests.all'/; + +# Op Cache Fetch Miss Ratio +cpu/event=0x28f,umask=0x4,name='op_cache_hit_miss.miss'/, +cpu/event=0x28f,umask=0x7,name='op_cache_hit_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Instruction Cache Fetch Miss Ratio +cpu/event=0x18e,umask=0x18,name='ic_tag_hit_miss.miss'/, +cpu/event=0x18e,umask=0x1f,name='ic_tag_hit_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from DRAM or IO in any NUMA node +cpu/event=0x44,umask=0x48,name='ls_any_fills_from_sys.dram_io_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from a different NUMA node +cpu/event=0x44,umask=0x50,name='ls_any_fills_from_sys.far_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from within the same CCX +cpu/event=0x44,umask=0x3,name='ls_any_fills_from_sys.local_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from another CCX cache in any NUMA node +cpu/event=0x44,umask=0x14,name='ls_any_fills_from_sys.remote_cache'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L1 Data Cache Fills +cpu/event=0x44,umask=0x5f,name='ls_any_fills_from_sys.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from local L2 +cpu/event=0x43,umask=0x1,name='ls_dmnd_fills_from_sys.local_l2'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from local L3 or different L2 in same CCX +cpu/event=0x43,umask=0x2,name='ls_dmnd_fills_from_sys.local_ccx'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from another CCX cache in the same NUMA node +cpu/event=0x43,umask=0x4,name='ls_dmnd_fills_from_sys.near_cache'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from DRAM or MMIO in the same NUMA node +cpu/event=0x43,umask=0x8,name='ls_dmnd_fills_from_sys.dram_io_near'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from another CCX cache in a different NUMA node +cpu/event=0x43,umask=0x10,name='ls_dmnd_fills_from_sys.far_cache'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from DRAM or MMIO in a different NUMA node +cpu/event=0x43,umask=0x40,name='ls_dmnd_fills_from_sys.dram_io_far'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Lines written per Write Combining Buffer close +cpu/event=0x50,umask=0x1,name='ls_wcb_close_flush.full_line_64b'/, +cpu/event=0x63,umask=0x20,name='l2_wcb_req.wcb_close'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 ITLB Misses +# L2 ITLB Misses and Instruction Page Walks +cpu/event=0x84,name='bp_l1_tlb_miss_l2_tlb_hit'/, +cpu/event=0x85,umask=0x7,name='bp_l1_tlb_miss_l2_tlb_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 DTLB Misses +cpu/event=0x45,umask=0xff,name='ls_l1_d_tlb_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 DTLB Misses and Data Page Walks +cpu/event=0x45,umask=0xf0,name='ls_l1_d_tlb_miss.all_l2_miss'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All TLBs Flushed +cpu/event=0x78,umask=0xff,name='ls_tlb_flush.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Macro-ops Dispatched +cpu/event=0xaa,umask=0x7,name='de_src_op_disp.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Mixed SSE and AVX Stalls +cpu/event=0xe,umask=0xe,name='fp_disp_faults.sse_avx_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Macro-ops Retired +cpu/event=0xc1,name='ex_ret_ops'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# DRAM read data bytes for local processor +df/event=0x1f,umask=0x7fe,name='local_processor_read_data_beats_cs0'/, +df/event=0x5f,umask=0x7fe,name='local_processor_read_data_beats_cs1'/, +df/event=0x9f,umask=0x7fe,name='local_processor_read_data_beats_cs2'/, +df/event=0xdf,umask=0x7fe,name='local_processor_read_data_beats_cs3'/, +df/event=0x11f,umask=0x7fe,name='local_processor_read_data_beats_cs4'/, +df/event=0x15f,umask=0x7fe,name='local_processor_read_data_beats_cs5'/, +df/event=0x19f,umask=0x7fe,name='local_processor_read_data_beats_cs6'/, +df/event=0x1df,umask=0x7fe,name='local_processor_read_data_beats_cs7'/, +df/event=0x21f,umask=0x7fe,name='local_processor_read_data_beats_cs8'/, +df/event=0x25f,umask=0x7fe,name='local_processor_read_data_beats_cs9'/, +df/event=0x29f,umask=0x7fe,name='local_processor_read_data_beats_cs10'/, +df/event=0x2df,umask=0x7fe,name='local_processor_read_data_beats_cs11'/; + +# DRAM write data bytes for local processor +df/event=0x1f,umask=0x7ff,name='local_processor_write_data_beats_cs0'/, +df/event=0x5f,umask=0x7ff,name='local_processor_write_data_beats_cs1'/, +df/event=0x9f,umask=0x7ff,name='local_processor_write_data_beats_cs2'/, +df/event=0xdf,umask=0x7ff,name='local_processor_write_data_beats_cs3'/, +df/event=0x11f,umask=0x7ff,name='local_processor_write_data_beats_cs4'/, +df/event=0x15f,umask=0x7ff,name='local_processor_write_data_beats_cs5'/, +df/event=0x19f,umask=0x7ff,name='local_processor_write_data_beats_cs6'/, +df/event=0x1df,umask=0x7ff,name='local_processor_write_data_beats_cs7'/, +df/event=0x21f,umask=0x7ff,name='local_processor_write_data_beats_cs8'/, +df/event=0x25f,umask=0x7ff,name='local_processor_write_data_beats_cs9'/, +df/event=0x29f,umask=0x7ff,name='local_processor_write_data_beats_cs10'/, +df/event=0x2df,umask=0x7ff,name='local_processor_write_data_beats_cs11'/; + +# DRAM read data bytes for remote processor +df/event=0x1f,umask=0xbfe,name='remote_processor_read_data_beats_cs0'/, +df/event=0x5f,umask=0xbfe,name='remote_processor_read_data_beats_cs1'/, +df/event=0x9f,umask=0xbfe,name='remote_processor_read_data_beats_cs2'/, +df/event=0xdf,umask=0xbfe,name='remote_processor_read_data_beats_cs3'/, +df/event=0x11f,umask=0xbfe,name='remote_processor_read_data_beats_cs4'/, +df/event=0x15f,umask=0xbfe,name='remote_processor_read_data_beats_cs5'/, +df/event=0x19f,umask=0xbfe,name='remote_processor_read_data_beats_cs6'/, +df/event=0x1df,umask=0xbfe,name='remote_processor_read_data_beats_cs7'/, +df/event=0x21f,umask=0xbfe,name='remote_processor_read_data_beats_cs8'/, +df/event=0x25f,umask=0xbfe,name='remote_processor_read_data_beats_cs9'/, +df/event=0x29f,umask=0xbfe,name='remote_processor_read_data_beats_cs10'/, +df/event=0x2df,umask=0xbfe,name='remote_processor_read_data_beats_cs11'/; + +# DRAM write data bytes for remote processor +df/event=0x1f,umask=0xbff,name='remote_processor_write_data_beats_cs0'/, +df/event=0x5f,umask=0xbff,name='remote_processor_write_data_beats_cs1'/, +df/event=0x9f,umask=0xbff,name='remote_processor_write_data_beats_cs2'/, +df/event=0xdf,umask=0xbff,name='remote_processor_write_data_beats_cs3'/, +df/event=0x11f,umask=0xbff,name='remote_processor_write_data_beats_cs4'/, +df/event=0x15f,umask=0xbff,name='remote_processor_write_data_beats_cs5'/, +df/event=0x19f,umask=0xbff,name='remote_processor_write_data_beats_cs6'/, +df/event=0x1df,umask=0xbff,name='remote_processor_write_data_beats_cs7'/, +df/event=0x21f,umask=0xbff,name='remote_processor_write_data_beats_cs8'/, +df/event=0x25f,umask=0xbff,name='remote_processor_write_data_beats_cs9'/, +df/event=0x29f,umask=0xbff,name='remote_processor_write_data_beats_cs10'/, +df/event=0x2df,umask=0xbff,name='remote_processor_write_data_beats_cs11'/; + +# Local socket upstream DMA read data bytes +df/event=0x81f,umask=0x7fe,name='local_socket_upstream_read_beats_iom0'/, +df/event=0x85f,umask=0x7fe,name='local_socket_upstream_read_beats_iom1'/, +df/event=0x89f,umask=0x7fe,name='local_socket_upstream_read_beats_iom2'/, +df/event=0x8df,umask=0x7fe,name='local_socket_upstream_read_beats_iom3'/; + +# Local socket upstream DMA write data bytes +df/event=0x81f,umask=0x7ff,name='local_socket_upstream_write_beats_iom0'/, +df/event=0x85f,umask=0x7ff,name='local_socket_upstream_write_beats_iom1'/, +df/event=0x89f,umask=0x7ff,name='local_socket_upstream_write_beats_iom2'/, +df/event=0x8df,umask=0x7ff,name='local_socket_upstream_write_beats_iom3'/; + +# Remote socket upstream DMA read data bytes +df/event=0x81f,umask=0xbfe,name='remote_socket_upstream_read_beats_iom0'/, +df/event=0x85f,umask=0xbfe,name='remote_socket_upstream_read_beats_iom1'/, +df/event=0x89f,umask=0xbfe,name='remote_socket_upstream_read_beats_iom2'/, +df/event=0x8df,umask=0xbfe,name='remote_socket_upstream_read_beats_iom3'/; + +# Remote socket upstream DMA write data bytes +df/event=0x81f,umask=0xbff,name='remote_socket_upstream_write_beats_iom0'/, +df/event=0x85f,umask=0xbff,name='remote_socket_upstream_write_beats_iom1'/, +df/event=0x89f,umask=0xbff,name='remote_socket_upstream_write_beats_iom2'/, +df/event=0x8df,umask=0xbff,name='remote_socket_upstream_write_beats_iom3'/; + +# Local socket inbound data bytes to the CPU +df/event=0x41e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm0'/, +df/event=0x45e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm1'/, +df/event=0x49e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm2'/, +df/event=0x4de,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm3'/, +df/event=0x51e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm4'/, +df/event=0x55e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm5'/, +df/event=0x59e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm6'/, +df/event=0x5de,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm7'/, +df/event=0x41f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm0'/, +df/event=0x45f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm1'/, +df/event=0x49f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm2'/, +df/event=0x4df,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm3'/, +df/event=0x51f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm4'/, +df/event=0x55f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm5'/, +df/event=0x59f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm6'/, +df/event=0x5df,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm7'/; + +# Local socket outbound data bytes from the CPU +df/event=0x41e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm0'/, +df/event=0x45e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm1'/, +df/event=0x49e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm2'/, +df/event=0x4de,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm3'/, +df/event=0x51e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm4'/, +df/event=0x55e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm5'/, +df/event=0x59e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm6'/, +df/event=0x5de,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm7'/, +df/event=0x41f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm0'/, +df/event=0x45f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm1'/, +df/event=0x49f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm2'/, +df/event=0x4df,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm3'/, +df/event=0x51f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm4'/, +df/event=0x55f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm5'/, +df/event=0x59f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm6'/, +df/event=0x5df,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm7'/; + +# Remote socket inbound data bytes to the CPU +df/event=0x41e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm0'/, +df/event=0x45e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm1'/, +df/event=0x49e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm2'/, +df/event=0x4de,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm3'/, +df/event=0x51e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm4'/, +df/event=0x55e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm5'/, +df/event=0x59e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm6'/, +df/event=0x5de,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm7'/, +df/event=0x41f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm0'/, +df/event=0x45f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm1'/, +df/event=0x49f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm2'/, +df/event=0x4df,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm3'/, +df/event=0x51f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm4'/, +df/event=0x55f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm5'/, +df/event=0x59f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm6'/, +df/event=0x5df,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm7'/; + +# Remote socket outbound data bytes from the CPU +df/event=0x41e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm0'/, +df/event=0x45e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm1'/, +df/event=0x49e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm2'/, +df/event=0x4de,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm3'/, +df/event=0x51e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm4'/, +df/event=0x55e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm5'/, +df/event=0x59e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm6'/, +df/event=0x5de,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm7'/, +df/event=0x41f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm0'/, +df/event=0x45f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm1'/, +df/event=0x49f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm2'/, +df/event=0x4df,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm3'/, +df/event=0x51f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm4'/, +df/event=0x55f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm5'/, +df/event=0x59f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm6'/, +df/event=0x5df,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm7'/; + +# Outbound data bytes from all links +df/event=0xb5f,umask=0xf3e,name='local_socket_outbound_data_beats_link0'/, +df/event=0xb9f,umask=0xf3e,name='local_socket_outbound_data_beats_link1'/, +df/event=0xbdf,umask=0xf3e,name='local_socket_outbound_data_beats_link2'/, +df/event=0xc1f,umask=0xf3e,name='local_socket_outbound_data_beats_link3'/, +df/event=0xc5f,umask=0xf3e,name='local_socket_outbound_data_beats_link4'/, +df/event=0xc9f,umask=0xf3e,name='local_socket_outbound_data_beats_link5'/, +df/event=0xcdf,umask=0xf3e,name='local_socket_outbound_data_beats_link6'/, +df/event=0xd1f,umask=0xf3e,name='local_socket_outbound_data_beats_link7'/; + +# Pipeline Utilization - Frontend Bound (%) +# Pipeline Utilization - Frontend Bound - Latency (%) +# Pipeline Utilization - Frontend Bound - Bandwidth (%) +cpu/event=0x1a0,umask=0x1,name='de_no_dispatch_per_slot.no_ops_from_frontend'/, +cpu/event=0x1a0,umask=0x1,cmask=0x6,name='de_no_dispatch_per_cycle.no_ops_from_frontend'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - Bad Speculation (%) +# Pipeline Utilization - Bad Speculation - Pipeline Restarts (%) +# Pipeline Utilization - Bad Speculation - Mispredicts (%) +cpu/event=0xaa,umask=0x7,name='de_src_op_disp.all'/, +cpu/event=0xc1,name='ex_ret_ops'/, +cpu/event=0xc3,name='ex_ret_brn_misp'/, +cpu/event=0x96,name='resyncs_or_nc_redirects'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - Backend Bound (%) +# Pipeline Utilization - Backend Bound - Memory (%) +# Pipeline Utilization - Backend Bound - CPU (%) +cpu/event=0x1a0,umask=0x1e,name='de_no_dispatch_per_slot.backend_stalls'/, +cpu/event=0xd6,umask=0xa2,name='ex_no_retire.load_not_complete'/, +cpu/event=0xd6,umask=0x2,name='ex_no_retire.not_complete'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - SMT Contention (%) +cpu/event=0x1a0,umask=0x60,name='de_no_dispatch_per_slot.smt_contention'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - Retiring (%) +# Pipeline Utilization - Retiring - Microcode (%) +# Pipeline Utilization - Retiring - Fastpath (%) +cpu/event=0xc1,name='ex_ret_ops'/, +cpu/event=0x1c2,name='ex_ret_ucode_ops'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Power +power/energy-pkg/; \ No newline at end of file diff --git a/cmd/metrics/resources/events/x86_64/AuthenticAMD/genoa.txt b/cmd/metrics/resources/events/x86_64/AuthenticAMD/genoa.txt new file mode 100644 index 0000000..791257c --- /dev/null +++ b/cmd/metrics/resources/events/x86_64/AuthenticAMD/genoa.txt @@ -0,0 +1,445 @@ +# Genoa event list + +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc:k'/k, +cpu-cycles, +cpu-cycles:k, +instructions, +instructions:k; + +# Branch Misprediction Ratio +cpu/event=0xc3,name='ex_ret_brn_misp'/, +cpu/event=0xc2,name='ex_ret_brn'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All Data Cache Accesses +cpu/event=0x29,umask=0x7,name='ls_dispatch.any'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L2 Cache Accesses +cpu/event=0x60,umask=0xf9,name='l2_request_g1.all_no_prefetch'/, +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu-cycles, +instructions; + +# L2 Cache Accesses from L1 Instruction Cache Misses +cpu/event=0x60,umask=0x10,name='l2_request_g1.cacheable_ic_read'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Accesses from L1 Data Cache Misses +cpu/event=0x60,umask=0xe8,name='l2_request_g1.all_dc'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Accesses from L2 Cache Hardware Prefetches +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L2 Cache Misses +cpu/event=0x64,umask=0x9,name='l2_cache_req_stat.ic_dc_miss_in_l2'/, +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Misses from L1 Instruction Cache Misses +cpu/event=0x64,umask=0x1,name='l2_cache_req_stat.ic_fill_miss'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Misses from L1 Data Cache Misses +cpu/event=0x64,umask=0x8,name='l2_cache_req_stat.ls_rd_blk_c'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Misses from L2 Cache Hardware Prefetches +cpu/event=0x71,umask=0x1f,name='l2_pf_miss_l2_hit_l3.all'/, +cpu/event=0x72,umask=0x1f,name='l2_pf_miss_l2_l3.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L2 Cache Hits +cpu/event=0x64,umask=0xf6,name='l2_cache_req_stat.ic_dc_hit_in_l2'/, +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Hits from L1 Instruction Cache Misses +cpu/event=0x64,umask=0x06,name='l2_cache_req_stat.ic_hit_in_l2'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Hits from L1 Data Cache Misses +cpu/event=0x64,umask=0xf0,name='l2_cache_req_stat.dc_hit_in_l2'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 Cache Hits from L2 Cache Hardware Prefetches +cpu/event=0x70,umask=0x1f,name='l2_pf_hit_l2.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L3 Cache Accesses +l3/event=0x4,umask=0xff,name='l3_lookup_state.all_coherent_accesses_to_l3'/; + +# L3 Cache Misses +l3/event=0x4,umask=0x1,name='l3_lookup_state.l3_miss'/; + +# L3 Cache Hits +l3/event=0x4,umask=0xfe,name='l3_lookup_state.l3_hit'/; + +# Average L3 Cache Read Miss Latency (ns) +l3/event=0xac,umask=0x3f,enallcores=0x1,enallslices=0x1,sliceid=0x3,threadmask=0x3,name='l3_xi_sampled_latency.all'/, +l3/event=0xad,umask=0x3f,enallcores=0x1,enallslices=0x1,sliceid=0x3,threadmask=0x3,name='l3_xi_sampled_latency_requests.all'/; + +# Op Cache Fetch Miss Ratio +cpu/event=0x28f,umask=0x4,name='op_cache_hit_miss.miss'/, +cpu/event=0x28f,umask=0x7,name='op_cache_hit_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Instruction Cache Fetch Miss Ratio +cpu/event=0x18e,umask=0x18,name='ic_tag_hit_miss.miss'/, +cpu/event=0x18e,umask=0x1f,name='ic_tag_hit_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from DRAM or IO in any NUMA node +cpu/event=0x44,umask=0x48,name='ls_any_fills_from_sys.dram_io_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from a different NUMA node +cpu/event=0x44,umask=0x50,name='ls_any_fills_from_sys.far_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from within the same CCX +cpu/event=0x44,umask=0x3,name='ls_any_fills_from_sys.local_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 Data Cache Fills from another CCX cache in any NUMA node +cpu/event=0x44,umask=0x14,name='ls_any_fills_from_sys.remote_cache'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All L1 Data Cache Fills +cpu/event=0x44,umask=0x5f,name='ls_any_fills_from_sys.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from local L2 +cpu/event=0x43,umask=0x1,name='ls_dmnd_fills_from_sys.local_l2'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from local L3 or different L2 in same CCX +cpu/event=0x43,umask=0x2,name='ls_dmnd_fills_from_sys.local_ccx'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from another CCX cache in the same NUMA node +cpu/event=0x43,umask=0x4,name='ls_dmnd_fills_from_sys.near_cache'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from DRAM or MMIO in the same NUMA node +cpu/event=0x43,umask=0x8,name='ls_dmnd_fills_from_sys.dram_io_near'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from another CCX cache in a different NUMA node +cpu/event=0x43,umask=0x10,name='ls_dmnd_fills_from_sys.far_cache'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Demand L1 Data Cache Fills from DRAM or MMIO in a different NUMA node +cpu/event=0x43,umask=0x40,name='ls_dmnd_fills_from_sys.dram_io_far'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Lines written per Write Combining Buffer close +cpu/event=0x50,umask=0x1,name='ls_wcb_close_flush.full_line_64b'/, +cpu/event=0x63,umask=0x20,name='l2_wcb_req.wcb_close'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 ITLB Misses +# L2 ITLB Misses and Instruction Page Walks +cpu/event=0x84,name='bp_l1_tlb_miss_l2_tlb_hit'/, +cpu/event=0x85,umask=0x7,name='bp_l1_tlb_miss_l2_tlb_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L1 DTLB Misses +cpu/event=0x45,umask=0xff,name='ls_l1_d_tlb_miss.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# L2 DTLB Misses and Data Page Walks +cpu/event=0x45,umask=0xf0,name='ls_l1_d_tlb_miss.all_l2_miss'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# All TLBs Flushed +cpu/event=0x78,umask=0xff,name='ls_tlb_flush.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Macro-ops Dispatched +cpu/event=0xaa,umask=0x7,name='de_src_op_disp.all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Mixed SSE and AVX Stalls +cpu/event=0xe,umask=0xe,name='fp_disp_faults.sse_avx_all'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# Macro-ops Retired +cpu/event=0xc1,name='ex_ret_ops'/, +cpu/event=0x120,umask=0x1,name='ls_not_halted_p0_cyc'/, +cpu-cycles, +instructions; + +# DRAM read data bytes for local processor +df/event=0x1f,umask=0x7fe,name='local_processor_read_data_beats_cs0'/, +df/event=0x5f,umask=0x7fe,name='local_processor_read_data_beats_cs1'/, +df/event=0x9f,umask=0x7fe,name='local_processor_read_data_beats_cs2'/, +df/event=0xdf,umask=0x7fe,name='local_processor_read_data_beats_cs3'/, +df/event=0x11f,umask=0x7fe,name='local_processor_read_data_beats_cs4'/, +df/event=0x15f,umask=0x7fe,name='local_processor_read_data_beats_cs5'/, +df/event=0x19f,umask=0x7fe,name='local_processor_read_data_beats_cs6'/, +df/event=0x1df,umask=0x7fe,name='local_processor_read_data_beats_cs7'/, +df/event=0x21f,umask=0x7fe,name='local_processor_read_data_beats_cs8'/, +df/event=0x25f,umask=0x7fe,name='local_processor_read_data_beats_cs9'/, +df/event=0x29f,umask=0x7fe,name='local_processor_read_data_beats_cs10'/, +df/event=0x2df,umask=0x7fe,name='local_processor_read_data_beats_cs11'/; + +# DRAM write data bytes for local processor +df/event=0x1f,umask=0x7ff,name='local_processor_write_data_beats_cs0'/, +df/event=0x5f,umask=0x7ff,name='local_processor_write_data_beats_cs1'/, +df/event=0x9f,umask=0x7ff,name='local_processor_write_data_beats_cs2'/, +df/event=0xdf,umask=0x7ff,name='local_processor_write_data_beats_cs3'/, +df/event=0x11f,umask=0x7ff,name='local_processor_write_data_beats_cs4'/, +df/event=0x15f,umask=0x7ff,name='local_processor_write_data_beats_cs5'/, +df/event=0x19f,umask=0x7ff,name='local_processor_write_data_beats_cs6'/, +df/event=0x1df,umask=0x7ff,name='local_processor_write_data_beats_cs7'/, +df/event=0x21f,umask=0x7ff,name='local_processor_write_data_beats_cs8'/, +df/event=0x25f,umask=0x7ff,name='local_processor_write_data_beats_cs9'/, +df/event=0x29f,umask=0x7ff,name='local_processor_write_data_beats_cs10'/, +df/event=0x2df,umask=0x7ff,name='local_processor_write_data_beats_cs11'/; + +# DRAM read data bytes for remote processor +df/event=0x1f,umask=0xbfe,name='remote_processor_read_data_beats_cs0'/, +df/event=0x5f,umask=0xbfe,name='remote_processor_read_data_beats_cs1'/, +df/event=0x9f,umask=0xbfe,name='remote_processor_read_data_beats_cs2'/, +df/event=0xdf,umask=0xbfe,name='remote_processor_read_data_beats_cs3'/, +df/event=0x11f,umask=0xbfe,name='remote_processor_read_data_beats_cs4'/, +df/event=0x15f,umask=0xbfe,name='remote_processor_read_data_beats_cs5'/, +df/event=0x19f,umask=0xbfe,name='remote_processor_read_data_beats_cs6'/, +df/event=0x1df,umask=0xbfe,name='remote_processor_read_data_beats_cs7'/, +df/event=0x21f,umask=0xbfe,name='remote_processor_read_data_beats_cs8'/, +df/event=0x25f,umask=0xbfe,name='remote_processor_read_data_beats_cs9'/, +df/event=0x29f,umask=0xbfe,name='remote_processor_read_data_beats_cs10'/, +df/event=0x2df,umask=0xbfe,name='remote_processor_read_data_beats_cs11'/; + +# DRAM write data bytes for remote processor +df/event=0x1f,umask=0xbff,name='remote_processor_write_data_beats_cs0'/, +df/event=0x5f,umask=0xbff,name='remote_processor_write_data_beats_cs1'/, +df/event=0x9f,umask=0xbff,name='remote_processor_write_data_beats_cs2'/, +df/event=0xdf,umask=0xbff,name='remote_processor_write_data_beats_cs3'/, +df/event=0x11f,umask=0xbff,name='remote_processor_write_data_beats_cs4'/, +df/event=0x15f,umask=0xbff,name='remote_processor_write_data_beats_cs5'/, +df/event=0x19f,umask=0xbff,name='remote_processor_write_data_beats_cs6'/, +df/event=0x1df,umask=0xbff,name='remote_processor_write_data_beats_cs7'/, +df/event=0x21f,umask=0xbff,name='remote_processor_write_data_beats_cs8'/, +df/event=0x25f,umask=0xbff,name='remote_processor_write_data_beats_cs9'/, +df/event=0x29f,umask=0xbff,name='remote_processor_write_data_beats_cs10'/, +df/event=0x2df,umask=0xbff,name='remote_processor_write_data_beats_cs11'/; + +# Local socket upstream DMA read data bytes +df/event=0x81f,umask=0x7fe,name='local_socket_upstream_read_beats_iom0'/, +df/event=0x85f,umask=0x7fe,name='local_socket_upstream_read_beats_iom1'/, +df/event=0x89f,umask=0x7fe,name='local_socket_upstream_read_beats_iom2'/, +df/event=0x8df,umask=0x7fe,name='local_socket_upstream_read_beats_iom3'/; + +# Local socket upstream DMA write data bytes +df/event=0x81f,umask=0x7ff,name='local_socket_upstream_write_beats_iom0'/, +df/event=0x85f,umask=0x7ff,name='local_socket_upstream_write_beats_iom1'/, +df/event=0x89f,umask=0x7ff,name='local_socket_upstream_write_beats_iom2'/, +df/event=0x8df,umask=0x7ff,name='local_socket_upstream_write_beats_iom3'/; + +# Remote socket upstream DMA read data bytes +df/event=0x81f,umask=0xbfe,name='remote_socket_upstream_read_beats_iom0'/, +df/event=0x85f,umask=0xbfe,name='remote_socket_upstream_read_beats_iom1'/, +df/event=0x89f,umask=0xbfe,name='remote_socket_upstream_read_beats_iom2'/, +df/event=0x8df,umask=0xbfe,name='remote_socket_upstream_read_beats_iom3'/; + +# Remote socket upstream DMA write data bytes +df/event=0x81f,umask=0xbff,name='remote_socket_upstream_write_beats_iom0'/, +df/event=0x85f,umask=0xbff,name='remote_socket_upstream_write_beats_iom1'/, +df/event=0x89f,umask=0xbff,name='remote_socket_upstream_write_beats_iom2'/, +df/event=0x8df,umask=0xbff,name='remote_socket_upstream_write_beats_iom3'/; + +# Local socket inbound data bytes to the CPU +df/event=0x41e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm0'/, +df/event=0x45e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm1'/, +df/event=0x49e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm2'/, +df/event=0x4de,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm3'/, +df/event=0x51e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm4'/, +df/event=0x55e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm5'/, +df/event=0x59e,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm6'/, +df/event=0x5de,umask=0x7fe,name='local_socket_inf0_inbound_data_beats_ccm7'/, +df/event=0x41f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm0'/, +df/event=0x45f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm1'/, +df/event=0x49f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm2'/, +df/event=0x4df,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm3'/, +df/event=0x51f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm4'/, +df/event=0x55f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm5'/, +df/event=0x59f,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm6'/, +df/event=0x5df,umask=0x7fe,name='local_socket_inf1_inbound_data_beats_ccm7'/; + +# Local socket outbound data bytes from the CPU +df/event=0x41e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm0'/, +df/event=0x45e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm1'/, +df/event=0x49e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm2'/, +df/event=0x4de,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm3'/, +df/event=0x51e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm4'/, +df/event=0x55e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm5'/, +df/event=0x59e,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm6'/, +df/event=0x5de,umask=0x7ff,name='local_socket_inf0_outbound_data_beats_ccm7'/, +df/event=0x41f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm0'/, +df/event=0x45f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm1'/, +df/event=0x49f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm2'/, +df/event=0x4df,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm3'/, +df/event=0x51f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm4'/, +df/event=0x55f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm5'/, +df/event=0x59f,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm6'/, +df/event=0x5df,umask=0x7ff,name='local_socket_inf1_outbound_data_beats_ccm7'/; + +# Remote socket inbound data bytes to the CPU +df/event=0x41e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm0'/, +df/event=0x45e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm1'/, +df/event=0x49e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm2'/, +df/event=0x4de,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm3'/, +df/event=0x51e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm4'/, +df/event=0x55e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm5'/, +df/event=0x59e,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm6'/, +df/event=0x5de,umask=0xbfe,name='remote_socket_inf0_inbound_data_beats_ccm7'/, +df/event=0x41f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm0'/, +df/event=0x45f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm1'/, +df/event=0x49f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm2'/, +df/event=0x4df,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm3'/, +df/event=0x51f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm4'/, +df/event=0x55f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm5'/, +df/event=0x59f,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm6'/, +df/event=0x5df,umask=0xbfe,name='remote_socket_inf1_inbound_data_beats_ccm7'/; + +# Remote socket outbound data bytes from the CPU +df/event=0x41e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm0'/, +df/event=0x45e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm1'/, +df/event=0x49e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm2'/, +df/event=0x4de,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm3'/, +df/event=0x51e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm4'/, +df/event=0x55e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm5'/, +df/event=0x59e,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm6'/, +df/event=0x5de,umask=0xbff,name='remote_socket_inf0_outbound_data_beats_ccm7'/, +df/event=0x41f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm0'/, +df/event=0x45f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm1'/, +df/event=0x49f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm2'/, +df/event=0x4df,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm3'/, +df/event=0x51f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm4'/, +df/event=0x55f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm5'/, +df/event=0x59f,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm6'/, +df/event=0x5df,umask=0xbff,name='remote_socket_inf1_outbound_data_beats_ccm7'/; + +# Outbound data bytes from all links +df/event=0xb5f,umask=0xf3e,name='local_socket_outbound_data_beats_link0'/, +df/event=0xb9f,umask=0xf3e,name='local_socket_outbound_data_beats_link1'/, +df/event=0xbdf,umask=0xf3e,name='local_socket_outbound_data_beats_link2'/, +df/event=0xc1f,umask=0xf3e,name='local_socket_outbound_data_beats_link3'/, +df/event=0xc5f,umask=0xf3e,name='local_socket_outbound_data_beats_link4'/, +df/event=0xc9f,umask=0xf3e,name='local_socket_outbound_data_beats_link5'/, +df/event=0xcdf,umask=0xf3e,name='local_socket_outbound_data_beats_link6'/, +df/event=0xd1f,umask=0xf3e,name='local_socket_outbound_data_beats_link7'/; + +# Pipeline Utilization - Frontend Bound (%) +# Pipeline Utilization - Frontend Bound - Latency (%) +# Pipeline Utilization - Frontend Bound - Bandwidth (%) +cpu/event=0x1a0,umask=0x1,name='de_no_dispatch_per_slot.no_ops_from_frontend'/, +cpu/event=0x1a0,umask=0x1,cmask=0x6,name='de_no_dispatch_per_cycle.no_ops_from_frontend'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - Bad Speculation (%) +# Pipeline Utilization - Bad Speculation - Pipeline Restarts (%) +# Pipeline Utilization - Bad Speculation - Mispredicts (%) +cpu/event=0xaa,umask=0x7,name='de_src_op_disp.all'/, +cpu/event=0xc1,name='ex_ret_ops'/, +cpu/event=0xc3,name='ex_ret_brn_misp'/, +cpu/event=0x96,name='resyncs_or_nc_redirects'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - Backend Bound (%) +# Pipeline Utilization - Backend Bound - Memory (%) +# Pipeline Utilization - Backend Bound - CPU (%) +cpu/event=0x1a0,umask=0x1e,name='de_no_dispatch_per_slot.backend_stalls'/, +cpu/event=0xd6,umask=0xa2,name='ex_no_retire.load_not_complete'/, +cpu/event=0xd6,umask=0x2,name='ex_no_retire.not_complete'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - SMT Contention (%) +cpu/event=0x1a0,umask=0x60,name='de_no_dispatch_per_slot.smt_contention'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Pipeline Utilization - Retiring (%) +# Pipeline Utilization - Retiring - Microcode (%) +# Pipeline Utilization - Retiring - Fastpath (%) +cpu/event=0xc1,name='ex_ret_ops'/, +cpu/event=0x1c2,name='ex_ret_ucode_ops'/, +cpu/event=0x76,name='ls_not_halted_cyc'/; + +# Power +power/energy-pkg/; \ No newline at end of file diff --git a/events/bdx.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/bdx.txt similarity index 96% rename from events/bdx.txt rename to cmd/metrics/resources/events/x86_64/GenuineIntel/bdx.txt index 692b9e5..1c944ae 100644 --- a/events/bdx.txt +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/bdx.txt @@ -1,9 +1,5 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - # Broadwell event list + cpu/event=0xc2,umask=0x02,period=2000003,name='UOPS_RETIRED.RETIRE_SLOTS'/, cpu/event=0xc5,umask=0x00,name='BR_MISP_RETIRED.ALL_BRANCHES'/, cpu/event=0xc3,umask=0x01,name='MACHINE_CLEARS.COUNT'/, diff --git a/events/clx_skx.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/clx.txt similarity index 97% rename from events/clx_skx.txt rename to cmd/metrics/resources/events/x86_64/GenuineIntel/clx.txt index 7ce3a76..1274780 100644 --- a/events/clx_skx.txt +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/clx.txt @@ -1,9 +1,5 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - # Cascadelake event list + #avx related power levels cpu/event=0x28,umask=0x07,period=200003,name='CORE_POWER.LVL0_TURBO_LICENSE'/, cpu/event=0x28,umask=0x18,period=200003,name='CORE_POWER.LVL1_TURBO_LICENSE'/, diff --git a/cmd/metrics/resources/events/x86_64/GenuineIntel/emr.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/emr.txt new file mode 100644 index 0000000..443b24a --- /dev/null +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/emr.txt @@ -0,0 +1,193 @@ +# EmeraldRapids event list + +cpu/event=0x51,umask=0x01,period=100003,name='L1D.REPLACEMENT'/, +cpu/event=0x24,umask=0xe4,period=200003,name='L2_RQSTS.ALL_CODE_RD'/, +cpu/event=0xd1,umask=0x01,period=1000003,name='MEM_LOAD_RETIRED.L1_HIT'/, +cpu/event=0x25,umask=0x1f,period=100003,name='L2_LINES_IN.ALL'/, +cpu/event=0xa6,umask=0x02,period=2000003,name='EXE_ACTIVITY.1_PORTS_UTIL'/, +cpu/event=0xa6,umask=0x04,period=2000003,name='EXE_ACTIVITY.2_PORTS_UTIL'/, +cpu/event=0xa6,umask=0x80,period=2000003,name='EXE_ACTIVITY.3_PORTS_UTIL:u0x80'/, +cpu/event=0xa6,umask=0xc,period=2000003,name='EXE_ACTIVITY.2_PORTS_UTIL:u0xc'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd1,umask=0x10,period=100021,name='MEM_LOAD_RETIRED.L2_MISS'/, +cpu/event=0x24,umask=0x24,period=200003,name='L2_RQSTS.CODE_RD_MISS'/, +cpu/event=0x11,umask=0x0e,period=100003,name='ITLB_MISSES.WALK_COMPLETED'/, +cpu/event=0x47,umask=0x03,cmask=0x03,period=1000003,name='MEMORY_ACTIVITY.STALLS_L1D_MISS'/, +cpu/event=0xa6,umask=0x40,cmask=0x02,period=1000003,name='EXE_ACTIVITY.BOUND_ON_STORES'/, +cpu/event=0xa6,umask=0x21,cmask=0x05,period=2000003,name='EXE_ACTIVITY.BOUND_ON_LOADS'/, +cpu/event=0xad,umask=0x10,period=1000003,name='INT_MISC.UOP_DROPPING'/, +cpu/event=0xad,umask=0x40,period=1000003,name='INT_MISC.UNKNOWN_BRANCH_CYCLES'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x12,umask=0x0e,period=100003,name='DTLB_LOAD_MISSES.WALK_COMPLETED'/, +cpu/event=0x12,umask=0x04,period=100003,name='DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M'/, +cpu/event=0x13,umask=0x0e,period=100003,name='DTLB_STORE_MISSES.WALK_COMPLETED'/, +cpu/event=0xd1,umask=0x02,period=200003,name='MEM_LOAD_RETIRED.L2_HIT'/, +cpu/event=0x3c,umask=0x02,period=25003,name='CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE'/, +cpu/event=0x3c,umask=0x08,period=2000003,name='CPU_CLK_UNHALTED.REF_DISTRIBUTED'/, +cpu/event=0xa2,umask=0x02,period=2000003,name='RESOURCE_STALLS.SCOREBOARD'/, +cpu/event=0xa3,umask=0x04,cmask=0x04,period=1000003,name='CYCLE_ACTIVITY.STALLS_TOTAL'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x00,umask=0x04,period=10000003,name='TOPDOWN.SLOTS'/, +cpu/event=0x00,umask=0x81,period=10000003,name='PERF_METRICS.BAD_SPECULATION'/, +cpu/event=0x00,umask=0x83,period=10000003,name='PERF_METRICS.BACKEND_BOUND'/, +cpu/event=0x00,umask=0x82,period=10000003,name='PERF_METRICS.FRONTEND_BOUND'/, +cpu/event=0x00,umask=0x80,period=10000003,name='PERF_METRICS.RETIRING'/, +cpu/event=0x00,umask=0x86,period=10000003,name='PERF_METRICS.FETCH_LATENCY'/, +cpu/event=0x00,umask=0x87,period=10000003,name='PERF_METRICS.MEMORY_BOUND'/, +cpu/event=0x00,umask=0x85,period=10000003,name='PERF_METRICS.BRANCH_MISPREDICTS'/, +cpu/event=0x00,umask=0x84,period=10000003,name='PERF_METRICS.HEAVY_OPERATIONS'/, +cpu/event=0x47,umask=0x09,cmask=0x09,period=1000003,name='MEMORY_ACTIVITY.STALLS_L3_MISS'/, +cpu/event=0x80,umask=0x04,period=500009,name='ICACHE_DATA.STALLS'/, +cpu/event=0x83,umask=0x04,period=200003,name='ICACHE_TAG.STALLS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xcf,umask=0x10,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED2.512B_PACKED_HALF'/, +cpu/event=0xcf,umask=0x08,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED2.256B_PACKED_HALF'/, +cpu/event=0xc7,umask=0x80,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.512B_PACKED_SINGLE'/, +cpu/event=0xc7,umask=0x40,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.512B_PACKED_DOUBLE'/, +cpu/event=0xc7,umask=0x20,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.256B_PACKED_SINGLE'/, +cpu/event=0xc7,umask=0x10,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.256B_PACKED_DOUBLE'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x47,umask=0x03,cmask=0x03,period=1000003,name='MEMORY_ACTIVITY.STALLS_L1D_MISS'/, +cpu/event=0x47,umask=0x05,cmask=0x05,period=1000003,name='MEMORY_ACTIVITY.STALLS_L2_MISS'/, +cpu/event=0x12,umask=0x20,cmask=0x01,period=100003,name='DTLB_LOAD_MISSES.STLB_HIT:c1'/, +cpu/event=0x12,umask=0x10,cmask=0x01,period=100003,name='DTLB_LOAD_MISSES.WALK_ACTIVE'/, +cpu/event=0xa3,umask=0x10,cmask=0x10,period=1000003,name='CYCLE_ACTIVITY.CYCLES_MEM_ANY'/, +cpu/event=0xb0,umask=0x09,cmask=0x01,period=1000003,name='ARITH.DIV_ACTIVE'/, +cpu/event=0xad,umask=0x80,period=500009,name='INT_MISC.CLEAR_RESTEER_CYCLES'/, +cpu/event=0xec,umask=0x02,period=2000003,name='CPU_CLK_UNHALTED.DISTRIBUTED'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd3,umask=0x10,cmask=0x00,period=100007,name='MEM_LOAD_L3_MISS_RETIRED.REMOTE_PMM'/, +cpu/event=0xd1,umask=0x08,cmask=0x00,period=200003,name='MEM_LOAD_RETIRED.L1_MISS'/, +cpu/event=0xd1,umask=0x80,cmask=0x00,period=1000003,name='MEM_LOAD_RETIRED.LOCAL_PMM'/, +cpu/event=0xb1,umask=0x01,cmask=0x03,period=2000003,name='UOPS_EXECUTED.CYCLES_GE_3'/, +cpu/event=0xb1,umask=0x01,cmask=0x00,period=2000003,name='UOPS_EXECUTED.THREAD'/, +cpu/event=0xb1,umask=0x10,cmask=0x00,period=2000003,name='UOPS_EXECUTED.X87'/, +cpu/event=0xc2,umask=0x04,period=2000003,name='UOPS_RETIRED.MS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd0,umask=0x21,cmask=0x00,period=1000003,name='MEM_INST_RETIRED.LOCK_LOADS'/, +cpu/event=0xd0,umask=0x82,cmask=0x00,period=1000003,name='MEM_INST_RETIRED.ALL_STORES'/, +cpu/event=0x24,umask=0xe2,cmask=0x00,period=2000003,name='L2_RQSTS.ALL_RFO'/, +cpu/event=0x24,umask=0xc2,cmask=0x00,period=2000003,name='L2_RQSTS.RFO_HIT'/, +cpu/event=0xcf,umask=0x1c,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED2.VECTOR'/, +cpu/event=0xc7,umask=0x3c,period=100003,name='FP_ARITH_INST_RETIRED.128B_PACKED_DOUBLE:u0x3c'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x2a,umask=0x01,cmask=0x00,offcore_rsp=0x8003C0001,name='OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HIT_WITH_FWD'/, +cpu/event=0x2a,umask=0x01,cmask=0x00,offcore_rsp=0x10003C0002,name='OCR.DEMAND_RFO.L3_HIT.SNOOP_HITM'/, +cpu/event=0x20,umask=0x04,cmask=0x01,period=1000003,name='OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO'/, +cpu/event=0xd1,umask=0x40,cmask=0x00,period=100007,name='MEM_LOAD_RETIRED.FB_HIT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x79,umask=0x04,cmask=0x01,period=2000003,name='IDQ.MITE_CYCLES_ANY'/, +cpu/event=0x79,umask=0x04,cmask=0x06,period=2000003,name='IDQ.MITE_CYCLES_OK'/, +cpu/event=0x79,umask=0x08,cmask=0x01,period=2000003,name='IDQ.DSB_CYCLES_ANY'/, +cpu/event=0x79,umask=0x08,cmask=0x06,period=2000003,name='IDQ.DSB_CYCLES_OK'/, +cpu/event=0xec,umask=0x02,period=2000003,name='CPU_CLK_UNHALTED.DISTRIBUTED'/, +cpu/event=0xb7,umask=0x02,period=2000003,name='EXE.AMX_BUSY'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd3,umask=0x02,cmask=0x00,period=1000003,name='MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM'/, +cpu/event=0xd3,umask=0x01,cmask=0x00,period=100007,name='MEM_LOAD_L3_MISS_RETIRED.LOCAL_DRAM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x104004477,name='OCR.READS_TO_CORE.LOCAL_DRAM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x730004477,name='OCR.READS_TO_CORE.REMOTE_DRAM'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd2,umask=0x02,cmask=0x00,period=20011,name='MEM_LOAD_L3_HIT_RETIRED.XSNP_NO_FWD'/, +cpu/event=0xd2,umask=0x04,cmask=0x00,period=20011,name='MEM_LOAD_L3_HIT_RETIRED.XSNP_FWD'/, +cpu/event=0x47,umask=0x02,cmask=0x02,period=1000003,name='MEMORY_ACTIVITY.CYCLES_L1D_MISS'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x90002380,name='OCR.HWPF_L3.REMOTE'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x2a,umask=0x01,cmask=0x00,offcore_rsp=0x1030004477,name='OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HITM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x84002380,name='OCR.HWPF_L3.L3_MISS_LOCAL'/, +cpu/event=0x20,umask=0x08,cmask=0x01,period=1000003,name='OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DATA_RD'/, +cpu/event=0x20,umask=0x08,cmask=0x04,period=1000003,name='OFFCORE_REQUESTS_OUTSTANDING.DATA_RD:c4'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xe7,umask=0x0c,cmask=0x00,period=100003,name='INT_VEC_RETIRED.ADD_256'/, +cpu/event=0xe7,umask=0x20,cmask=0x00,period=100003,name='INT_VEC_RETIRED.VNNI_256'/, +cpu/event=0xe7,umask=0x80,cmask=0x00,period=100003,name='INT_VEC_RETIRED.MUL_256'/, +cpu/event=0x79,umask=0x08,cmask=0x00,period=2000003,name='IDQ.DSB_UOPS'/, +cpu/event=0x79,umask=0x04,period=100003,name='IDQ.MITE_UOPS'/, +cpu/event=0x79,umask=0x20,period=100003,name='IDQ.MS_UOPS'/, +cpu/event=0xa8,umask=0x01,cmask=0x00,period=2000003,name='LSD.UOPS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd3,umask=0x08,cmask=0x00,period=100007,name='MEM_LOAD_L3_MISS_RETIRED.REMOTE_FWD'/, +cpu/event=0xd3,umask=0x04,cmask=0x00,period=100007,name='MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x1030004477,name='OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x830004477,name='OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD'/, +cpu-cycles:k, +ref-cycles:k, +instructions:k; + +#C6 +cstate_core/c6-residency/; +cstate_pkg/c6-residency/; + +#UPI +upi/event=0x02,umask=0x0f,name='UNC_UPI_TxL_FLITS.ALL_DATA'/; + +#CHA (Cache) +cha/event=0x35,umask=0xc80ffe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_CRD'/, +cha/event=0x35,umask=0xc8177e01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE'/, +cha/event=0x36,umask=0xc8177e01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE'/; + +cha/event=0x35,umask=0xC816FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL'/, +cha/event=0x36,umask=0xc816fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL'/, +cha/event=0x35,umask=0xC896FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL'/, +cha/event=0x35,umask=0xC8977E01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE'/; + +cha/event=0x35,umask=0xccd7fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA'/, +cha/event=0x35,umask=0xc817fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD'/, +cha/event=0x35,umask=0xc897fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF'/, +cha/event=0x36,umask=0xC817fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD'/; + +#CHA (IO Bandwidth) +cha/event=0x35,umask=0xc8f3ff04,name='UNC_CHA_TOR_INSERTS.IO_PCIRDCUR'/, +cha/event=0x35,umask=0xCC43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOM'/, +cha/event=0x35,umask=0xCD43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR'/, +cha/event=0x01,umask=0x00,name='UNC_CHA_CLOCKTICKS'/; + +#IMC (memory read/writes) +imc/event=0x05,umask=0xcf,name='UNC_M_CAS_COUNT.RD'/, +imc/event=0x05,umask=0xf0,name='UNC_M_CAS_COUNT.WR'/; + +#power +power/energy-pkg/, +power/energy-ram/; diff --git a/events/spr_emr_nofixedtma.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/emr_nofixedtma.txt similarity index 93% rename from events/spr_emr_nofixedtma.txt rename to cmd/metrics/resources/events/x86_64/GenuineIntel/emr_nofixedtma.txt index d767656..974cea6 100644 --- a/events/spr_emr_nofixedtma.txt +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/emr_nofixedtma.txt @@ -1,9 +1,4 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -# Sapphire Rapids and Emerald Rapids event list for platforms that don't have support for the fixed counter +# Emerald Rapids event list for platforms that don't have support for the fixed counter # TMA events, e.g., some AWS VMs. # Note that there are no more than 10 events per group. On these same platforms, the cpu-cycles fixed # counter is not supported so a general purpose counter will be used. diff --git a/cmd/metrics/resources/events/x86_64/GenuineIntel/gnr.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/gnr.txt new file mode 100644 index 0000000..fce7219 --- /dev/null +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/gnr.txt @@ -0,0 +1,175 @@ +# GraniteRapids event list + +cpu/event=0x51,umask=0x01,period=100003,name='L1D.REPLACEMENT'/, +cpu/event=0x24,umask=0xe4,period=200003,name='L2_RQSTS.ALL_CODE_RD'/, +cpu/event=0xd1,umask=0x01,period=1000003,name='MEM_LOAD_RETIRED.L1_HIT'/, +cpu/event=0x25,umask=0x1f,period=100003,name='L2_LINES_IN.ALL'/, +cpu/event=0xa6,umask=0x02,period=2000003,name='EXE_ACTIVITY.1_PORTS_UTIL'/, +cpu/event=0xa6,umask=0x04,period=2000003,name='EXE_ACTIVITY.2_PORTS_UTIL'/, +cpu/event=0xa6,umask=0xc,period=2000003,name='EXE_ACTIVITY.2_PORTS_UTIL:u0xc'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd1,umask=0x10,period=100021,name='MEM_LOAD_RETIRED.L2_MISS'/, +cpu/event=0x24,umask=0x24,period=200003,name='L2_RQSTS.CODE_RD_MISS'/, +cpu/event=0x11,umask=0x0e,period=100003,name='ITLB_MISSES.WALK_COMPLETED'/, +cpu/event=0x47,umask=0x03,cmask=0x03,period=1000003,name='MEMORY_ACTIVITY.STALLS_L1D_MISS'/, +cpu/event=0xa6,umask=0x40,cmask=0x02,period=1000003,name='EXE_ACTIVITY.BOUND_ON_STORES'/, +cpu/event=0xa6,umask=0x21,cmask=0x05,period=2000003,name='EXE_ACTIVITY.BOUND_ON_LOADS'/, +cpu/event=0xad,umask=0x10,period=1000003,name='INT_MISC.UOP_DROPPING'/, +cpu/event=0xad,umask=0x40,period=1000003,name='INT_MISC.UNKNOWN_BRANCH_CYCLES'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x12,umask=0x0e,period=100003,name='DTLB_LOAD_MISSES.WALK_COMPLETED'/, +cpu/event=0x12,umask=0x04,period=100003,name='DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M'/, +cpu/event=0x13,umask=0x0e,period=100003,name='DTLB_STORE_MISSES.WALK_COMPLETED'/, +cpu/event=0xd1,umask=0x02,period=200003,name='MEM_LOAD_RETIRED.L2_HIT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x00,umask=0x04,period=10000003,name='TOPDOWN.SLOTS'/, +cpu/event=0x00,umask=0x81,period=10000003,name='PERF_METRICS.BAD_SPECULATION'/, +cpu/event=0x00,umask=0x83,period=10000003,name='PERF_METRICS.BACKEND_BOUND'/, +cpu/event=0x00,umask=0x82,period=10000003,name='PERF_METRICS.FRONTEND_BOUND'/, +cpu/event=0x00,umask=0x80,period=10000003,name='PERF_METRICS.RETIRING'/, +cpu/event=0x00,umask=0x86,period=10000003,name='PERF_METRICS.FETCH_LATENCY'/, +cpu/event=0x00,umask=0x87,period=10000003,name='PERF_METRICS.MEMORY_BOUND'/, +cpu/event=0x00,umask=0x85,period=10000003,name='PERF_METRICS.BRANCH_MISPREDICTS'/, +cpu/event=0x00,umask=0x84,period=10000003,name='PERF_METRICS.HEAVY_OPERATIONS'/, +cpu/event=0x47,umask=0x09,cmask=0x09,period=1000003,name='MEMORY_ACTIVITY.STALLS_L3_MISS'/, +cpu/event=0x80,umask=0x04,period=500009,name='ICACHE_DATA.STALLS'/, +cpu/event=0x83,umask=0x04,period=200003,name='ICACHE_TAG.STALLS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xcf,umask=0x10,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED2.512B_PACKED_HALF'/, +cpu/event=0xcf,umask=0x08,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED2.256B_PACKED_HALF'/, +cpu/event=0xc7,umask=0x80,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.512B_PACKED_SINGLE'/, +cpu/event=0xc7,umask=0x40,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.512B_PACKED_DOUBLE'/, +cpu/event=0xc7,umask=0x20,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.256B_PACKED_SINGLE'/, +cpu/event=0xc7,umask=0x10,cmask=0x00,period=100003,name='FP_ARITH_INST_RETIRED.256B_PACKED_DOUBLE'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x47,umask=0x03,cmask=0x03,period=1000003,name='MEMORY_ACTIVITY.STALLS_L1D_MISS'/, +cpu/event=0x47,umask=0x05,cmask=0x05,period=1000003,name='MEMORY_ACTIVITY.STALLS_L2_MISS'/, +cpu/event=0x12,umask=0x20,cmask=0x01,period=100003,name='DTLB_LOAD_MISSES.STLB_HIT:c1'/, +cpu/event=0x12,umask=0x10,cmask=0x01,period=100003,name='DTLB_LOAD_MISSES.WALK_ACTIVE'/, +cpu/event=0xa3,umask=0x10,cmask=0x10,period=1000003,name='CYCLE_ACTIVITY.CYCLES_MEM_ANY'/, +cpu/event=0xad,umask=0x80,period=500009,name='INT_MISC.CLEAR_RESTEER_CYCLES'/, +cpu/event=0xec,umask=0x02,period=2000003,name='CPU_CLK_UNHALTED.DISTRIBUTED'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd1,umask=0x08,cmask=0x00,period=200003,name='MEM_LOAD_RETIRED.L1_MISS'/, +cpu/event=0xb1,umask=0x01,cmask=0x03,period=2000003,name='UOPS_EXECUTED.CYCLES_GE_3'/, +cpu/event=0xc2,umask=0x04,period=2000003,name='UOPS_RETIRED.MS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd0,umask=0x21,cmask=0x00,period=1000003,name='MEM_INST_RETIRED.LOCK_LOADS'/, +cpu/event=0xd0,umask=0x82,cmask=0x00,period=1000003,name='MEM_INST_RETIRED.ALL_STORES'/, +cpu/event=0x24,umask=0xe2,cmask=0x00,period=2000003,name='L2_RQSTS.ALL_RFO'/, +cpu/event=0x24,umask=0xc2,cmask=0x00,period=2000003,name='L2_RQSTS.RFO_HIT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x2a,umask=0x01,cmask=0x00,offcore_rsp=0x8003C0001,name='OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HIT_WITH_FWD'/, +cpu/event=0x2a,umask=0x01,cmask=0x00,offcore_rsp=0x10003C0002,name='OCR.DEMAND_RFO.L3_HIT.SNOOP_HITM'/, +cpu/event=0x20,umask=0x04,cmask=0x01,period=1000003,name='OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO'/, +cpu/event=0xd1,umask=0x40,cmask=0x00,period=100007,name='MEM_LOAD_RETIRED.FB_HIT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd3,umask=0x02,cmask=0x00,period=1000003,name='MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM'/, +cpu/event=0xd3,umask=0x01,cmask=0x00,period=100007,name='MEM_LOAD_L3_MISS_RETIRED.LOCAL_DRAM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x104004477,name='OCR.READS_TO_CORE.LOCAL_DRAM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x730004477,name='OCR.READS_TO_CORE.REMOTE_DRAM'/, +cpu/event=0xec,umask=0x02,period=2000003,name='CPU_CLK_UNHALTED.DISTRIBUTED'/, +cpu/event=0xb7,umask=0x02,period=2000003,name='EXE.AMX_BUSY'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd2,umask=0x02,cmask=0x00,period=20011,name='MEM_LOAD_L3_HIT_RETIRED.XSNP_NO_FWD'/, +cpu/event=0xd2,umask=0x04,cmask=0x00,period=20011,name='MEM_LOAD_L3_HIT_RETIRED.XSNP_FWD'/, +cpu/event=0x47,umask=0x02,cmask=0x02,period=1000003,name='MEMORY_ACTIVITY.CYCLES_L1D_MISS'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x90002380,name='OCR.HWPF_L3.REMOTE'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x2a,umask=0x01,cmask=0x00,offcore_rsp=0x1030004477,name='OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HITM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x84002380,name='OCR.HWPF_L3.L3_MISS_LOCAL'/, +cpu/event=0x20,umask=0x08,cmask=0x01,period=1000003,name='OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DATA_RD'/, +cpu/event=0x20,umask=0x08,cmask=0x04,period=1000003,name='OFFCORE_REQUESTS_OUTSTANDING.DATA_RD:c4'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xe7,umask=0x0c,cmask=0x00,period=100003,name='INT_VEC_RETIRED.ADD_256'/, +cpu/event=0xe7,umask=0x20,cmask=0x00,period=100003,name='INT_VEC_RETIRED.VNNI_256'/, +cpu/event=0xe7,umask=0x80,cmask=0x00,period=100003,name='INT_VEC_RETIRED.MUL_256'/, +cpu/event=0x79,umask=0x08,cmask=0x00,period=2000003,name='IDQ.DSB_UOPS'/, +cpu/event=0x79,umask=0x04,period=100003,name='IDQ.MITE_UOPS'/, +cpu/event=0x79,umask=0x20,period=100003,name='IDQ.MS_UOPS'/, +cpu/event=0xa8,umask=0x01,cmask=0x00,period=2000003,name='LSD.UOPS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd3,umask=0x08,cmask=0x00,period=100007,name='MEM_LOAD_L3_MISS_RETIRED.REMOTE_FWD'/, +cpu/event=0xd3,umask=0x04,cmask=0x00,period=100007,name='MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x1030004477,name='OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x830004477,name='OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD'/, +cpu-cycles:k, +ref-cycles:k, +instructions:k; + +#C6 +cstate_core/c6-residency/; +cstate_pkg/c6-residency/; + +#UPI +upi/event=0x02,umask=0x0f,name='UNC_UPI_TxL_FLITS.ALL_DATA'/; + +#CHA (Cache) +cha/event=0x35,umask=0xc80ffe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_CRD'/, +cha/event=0x35,umask=0xc8177e01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE'/, +cha/event=0x36,umask=0xc8177e01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE'/; + +cha/event=0x35,umask=0xC816FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL'/, +cha/event=0x36,umask=0xc816fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL'/, +cha/event=0x35,umask=0xC896FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL'/, +cha/event=0x35,umask=0xC8977E01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE'/; + +cha/event=0x35,umask=0xccd7fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA'/, +cha/event=0x35,umask=0xc817fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD'/, +cha/event=0x35,umask=0xc897fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF'/, +cha/event=0x36,umask=0xC817fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD'/; + +#CHA (IO Bandwidth) +cha/event=0x35,umask=0xc8f3ff04,name='UNC_CHA_TOR_INSERTS.IO_PCIRDCUR'/, +cha/event=0x35,umask=0xCC43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOM'/, +cha/event=0x35,umask=0xCD43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR'/, +cha/event=0x01,umask=0x00,name='UNC_CHA_CLOCKTICKS'/; + +#IMC (memory read/writes) +imc/event=0x05,umask=0xcf,name='UNC_M_CAS_COUNT_SCH0.RD'/, +imc/event=0x06,umask=0xcf,name='UNC_M_CAS_COUNT_SCH1.RD'/, +imc/event=0x05,umask=0xf0,name='UNC_M_CAS_COUNT_SCH0.WR'/, +imc/event=0x06,umask=0xf0,name='UNC_M_CAS_COUNT_SCH1.WR'/; + +#power +power/energy-pkg/, +power/energy-ram/; diff --git a/events/icx.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/icx.txt similarity index 97% rename from events/icx.txt rename to cmd/metrics/resources/events/x86_64/GenuineIntel/icx.txt index 4d3457b..acdaa7b 100644 --- a/events/icx.txt +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/icx.txt @@ -1,9 +1,5 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - # Icelake event list + cpu/event=0x51,umask=0x01,period=100003,name='L1D.REPLACEMENT'/, cpu/event=0xd1,umask=0x01,period=1000003,name='MEM_LOAD_RETIRED.L1_HIT'/, cpu/event=0x24,umask=0xe4,period=200003,name='L2_RQSTS.ALL_CODE_RD'/, diff --git a/events/icx_nofixedtma.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/icx_nofixedtma.txt similarity index 95% rename from events/icx_nofixedtma.txt rename to cmd/metrics/resources/events/x86_64/GenuineIntel/icx_nofixedtma.txt index 00c16cc..00aa643 100644 --- a/events/icx_nofixedtma.txt +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/icx_nofixedtma.txt @@ -1,8 +1,3 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - # Icelake event list for platforms that don't have support for the fixed counter TMA events, e.g., some AWS # VMs. # Note that there are no more than 10 events per group. On these same platforms, the cpu-cycles fixed diff --git a/cmd/metrics/resources/events/x86_64/GenuineIntel/skx.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/skx.txt new file mode 100644 index 0000000..1965cca --- /dev/null +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/skx.txt @@ -0,0 +1,235 @@ +# Skylake event list + +#avx related power levels +cpu/event=0x28,umask=0x07,period=200003,name='CORE_POWER.LVL0_TURBO_LICENSE'/, +cpu/event=0x28,umask=0x18,period=200003,name='CORE_POWER.LVL1_TURBO_LICENSE'/, +cpu/event=0x28,umask=0x20,period=200003,name='CORE_POWER.LVL2_TURBO_LICENSE'/, +cpu/event=0x51,umask=0x01,period=2000003,name='L1D.REPLACEMENT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xc3,umask=0x01,edge,period=100003,name='MACHINE_CLEARS.COUNT'/, +cpu/event=0xc5,umask=0x00,period=400009,name='BR_MISP_RETIRED.ALL_BRANCHES'/, +cpu/event=0x0d,umask=0x80,period=2000003,name='INT_MISC.CLEAR_RESTEER_CYCLES'/, +cpu/event=0xd1,umask=0x01,period=2000003,name='MEM_LOAD_RETIRED.L1_HIT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x0e,umask=0x01,period=2000003,name='UOPS_ISSUED.ANY'/, +cpu/event=0xc2,umask=0x02,period=2000003,name='UOPS_RETIRED.RETIRE_SLOTS'/, +cpu/event=0x0d,umask=0x01,period=2000003,name='INT_MISC.RECOVERY_CYCLES_ANY'/, +cpu/event=0x0d,umask=0x01,period=2000003,name='INT_MISC.RECOVERY_CYCLES'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x3c,umask=0x0,period=2000003,name='CPU_CLK_UNHALTED.THREAD_ANY'/, +cpu/event=0x9c,umask=0x01,period=2000003,name='IDQ_UOPS_NOT_DELIVERED.CORE'/, +cpu/event=0xa3,umask=0x14,cmask=0x14,period=2000003,name='CYCLE_ACTIVITY.STALLS_MEM_ANY'/, +cpu/event=0xa3,umask=0x04,cmask=0x04,period=2000003,name='CYCLE_ACTIVITY.STALLS_TOTAL'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xa6,umask=0x02,period=2000003,name='EXE_ACTIVITY.1_PORTS_UTIL'/, +cpu/event=0xa6,umask=0x04,period=2000003,name='EXE_ACTIVITY.2_PORTS_UTIL'/, +cpu/event=0xa6,umask=0x40,period=2000003,name='EXE_ACTIVITY.BOUND_ON_STORES'/, +cpu/event=0xa6,umask=0x01,period=2000003,name='EXE_ACTIVITY.EXE_BOUND_0_PORTS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd1,umask=0x02,period=100003,name='MEM_LOAD_RETIRED.L2_HIT'/, +cpu/event=0xd1,umask=0x40,period=100007,name='MEM_LOAD_RETIRED.FB_HIT'/, +cpu/event=0xd1,umask=0x08,period=100003,name='MEM_LOAD_RETIRED.L1_MISS'/, +cpu/event=0x48,umask=0x02,cmask=0x01,period=2000003,name='L1D_PEND_MISS.FB_FULL:c1'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xa3,umask=0x0c,cmask=0x0c,period=2000003,name='CYCLE_ACTIVITY.STALLS_L1D_MISS'/, +cpu/event=0xa3,umask=0x05,cmask=0x05,period=2000003,name='CYCLE_ACTIVITY.STALLS_L2_MISS'/, +cpu/event=0xa3,umask=0x06,cmask=0x06,period=2000003,name='CYCLE_ACTIVITY.STALLS_L3_MISS'/, +cpu/event=0x60,umask=0x04,cmask=0x01,period=200003,name='OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x85,umask=0x0e,period=100003,name='ITLB_MISSES.WALK_COMPLETED'/, +cpu/event=0x85,umask=0x10,period=100003,name='ITLB_MISSES.WALK_ACTIVE'/, +cpu/event=0x08,umask=0x0e,period=100003,name='DTLB_LOAD_MISSES.WALK_COMPLETED'/, +cpu/event=0x08,umask=0x10,cmask=0x01,period=100003,name='DTLB_LOAD_MISSES.WALK_ACTIVE'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x49,umask=0x0e,period=100003,name='DTLB_STORE_MISSES.WALK_COMPLETED'/, +cpu/event=0x49,umask=0x10,period=100003,name='DTLB_STORE_MISSES.WALK_ACTIVE'/, +cpu/event=0x9c,umask=0x01,cmask=0x4,period=2000003,name='IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE'/, +cpu/event=0xe6,umask=0x01,period=100003,name='BACLEARS.ANY'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd0,umask=0x21,cmask=0x01,period=100007,name='MEM_INST_RETIRED.LOCK_LOADS'/, +cpu/event=0x24,umask=0xe2,cmask=0x00,period=200003,name='L2_RQSTS.ALL_RFO'/, +cpu/event=0xd0,umask=0x82,cmask=0x00,period=200003,name='MEM_INST_RETIRED.ALL_STORES'/, +cpu/event=0x24,umask=0xc2,cmask=0x00,period=200003,name='L2_RQSTS.RFO_HIT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xb7,umask=0x01,offcore_rsp=0x10003C0001,period=100003,name='OCR.DEMAND_DATA_RD.L3_HIT.HITM_OTHER_CORE'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x8003C0001,period=100003,name='OCR.DEMAND_DATA_RD.L3_HIT.HIT_OTHER_CORE_FWD'/, +cpu/event=0x60,umask=0x10,cmask=0x06,period=2000003,name='OFFCORE_REQUESTS_OUTSTANDING.L3_MISS_DEMAND_DATA_RD_GE_6'/, +cpu/event=0x60,umask=0x10,cmask=0x01,period=2000003,name='OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_L3_MISS_DEMAND_DATA_RD'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xb1,umask=0x02,cmask=0x1,period=2000003,name='UOPS_EXECUTED.CORE_CYCLES_GE_1'/, +cpu/event=0xb1,umask=0x02,cmask=0x2,period=2000003,name='UOPS_EXECUTED.CORE_CYCLES_GE_2'/, +cpu/event=0xb1,umask=0x02,cmask=0x3,period=2000003,name='UOPS_EXECUTED.CORE_CYCLES_GE_3'/, +cpu/event=0xc2,umask=0x04,cmask=0x00,period=2000003,name='UOPS_RETIRED.MACRO_FUSED'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xc7,umask=0x03,cmask=0x00,period=2000003,name='FP_ARITH_INST_RETIRED.SCALAR_SINGLE:u0x03'/, +cpu/event=0xc7,umask=0xfc,cmask=0x00,period=2000003,name='FP_ARITH_INST_RETIRED.128B_PACKED_DOUBLE:u0xfc'/, +cpu/event=0x80,umask=0x4,name='ICACHE_16B.IFDATA_STALL'/, +cpu/event=0x80,umask=0x4,cmask=0x1,edge=0x1,name='ICACHE_16B.IFDATA_STALL:c1:e1'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x24,umask=0xe4,period=200003,name='L2_RQSTS.ALL_CODE_RD'/, +cpu/event=0x85,umask=0x04,period=100003,name='ITLB_MISSES.WALK_COMPLETED_2M_4M'/, +cpu/event=0xf1,umask=0x1f,period=100003,name='L2_LINES_IN.ALL'/, +cpu/event=0xd1,umask=0x10,period=50021,name='MEM_LOAD_RETIRED.L2_MISS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x24,umask=0x24,period=200003,name='L2_RQSTS.CODE_RD_MISS'/, +cpu/event=0x08,umask=0x04,period=2000003,name='DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M'/, +cpu/event=0x08,umask=0x02,period=2000003,name='DTLB_LOAD_MISSES.WALK_COMPLETED_4K'/, +cpu/event=0x08,umask=0x08,period=2000003,name='DTLB_LOAD_MISSES.WALK_COMPLETED_1G'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd1,umask=0x40,period=100007,name='MEM_LOAD_RETIRED.FB_HIT'/, +cpu/event=0xa3,umask=0x10,cmask=0x16,period=2000003,name='CYCLE_ACTIVITY.CYCLES_MEM_ANY'/, +cpu/event=0xa3,umask=0x08,cmask=0x08,period=2000003,name='CYCLE_ACTIVITY.CYCLES_L1D_MISS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x79,umask=0x30,period=2000003,name='IDQ.MS_UOPS'/, +cpu/event=0x83,umask=0x04,period=200003,name='ICACHE_64B.IFTAG_STALL'/, +cpu/event=0x08,umask=0x20,cmask=0x01,period=2000003,name='DTLB_LOAD_MISSES.STLB_HIT:c1'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x80,umask=0x4,name='ICACHE_16B.IFDATA_STALL'/, +cpu/event=0x80,umask=0x4,cmask=0x1,edge=0x1,name='ICACHE_16B.IFDATA_STALL:c1:e1'/, +cpu/event=0x14,umask=0x01,period=2000003,name='ARITH.DIVIDER_ACTIVE'/, +cpu/event=0xb1,umask=0x02,inv=0x1,cmask=0x1,period=2000003,name='UOPS_EXECUTED.CORE_CYCLES_NONE'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x3c,umask=0x2,name='CPU_CLK_THREAD_UNHALTED.ONE_THREAD_ACTIVE'/, +cpu/event=0x3c,umask=0x1,name='CPU_CLK_THREAD_UNHALTED.REF_XCLK_ANY'/, +cpu-cycles:k, +ref-cycles:k, +instructions:k; + +cpu/event=0x79,umask=0x08,cmask=0x00,period=2000003,name='IDQ.DSB_UOPS'/, +cpu/event=0x79,umask=0x04,cmask=0x00,period=2000003,name='IDQ.MITE_UOPS'/, +cpu/event=0xa8,umask=0x01,cmask=0x00,period=2000003,name='LSD.UOPS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x79,umask=0x24,cmask=0x01,period=2000003,name='IDQ.ALL_MITE_CYCLES_ANY_UOPS'/, +cpu/event=0x79,umask=0x24,cmask=0x04,period=2000003,name='IDQ.ALL_MITE_CYCLES_4_UOPS'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x10003C0002,period=100003,name='OCR.DEMAND_RFO.L3_HIT.HITM_OTHER_CORE'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x10003C0020,period=100003,name='OCR.PF_L2_RFO.L3_HIT.HITM_OTHER_CORE'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x79,umask=0x18,cmask=0x01,period=2000003,name='IDQ.ALL_DSB_CYCLES_ANY_UOPS'/, +cpu/event=0x79,umask=0x18,cmask=0x04,period=2000003,name='IDQ.ALL_DSB_CYCLES_4_UOPS'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x103FC00002,period=100003,name='OCR.DEMAND_RFO.L3_MISS.REMOTE_HITM'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x103FC00020,period=100003,name='OCR.PF_L2_RFO.L3_MISS.REMOTE_HITM'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd2,umask=0x02,cmask=0x00,period=20011,name='MEM_LOAD_L3_HIT_RETIRED.XSNP_HIT'/, +cpu/event=0xd2,umask=0x04,cmask=0x00,period=20011,name='MEM_LOAD_L3_HIT_RETIRED.XSNP_HITM'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x3F840007F7,name='OCR.ALL_READS.L3_MISS_LOCAL_DRAM.ANY_SNOOP'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x3FB80007F7,name='OCR.ALL_READS.L3_MISS_LOCAL_DRAM.ANY_SNOOP_ocr_msr_3fB80007f7'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xb1,umask=0x10,cmask=0x00,period=2000003,name='UOPS_EXECUTED.X87'/, +cpu/event=0xb1,umask=0x01,cmask=0x00,period=2000003,name='UOPS_EXECUTED.THREAD'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x103FC007F7,name='OCR.ALL_READS.L3_MISS.REMOTE_HITM'/, +cpu/event=0xb7,umask=0x01,offcore_rsp=0x083FC007F7,name='OCR.ALL_READS.L3_MISS.REMOTE_HIT_FORWARD'/, +cpu-cycles, +ref-cycles, +instructions; + +#C6 +cstate_core/c6-residency/; +cstate_pkg/c6-residency/; + +#memory read/writes +imc/event=0x04,umask=0x03,name='UNC_M_CAS_COUNT.RD'/, +imc/event=0x04,umask=0x0c,name='UNC_M_CAS_COUNT.WR'/; + +cha/event=0x0,umask=0x0,name='UNC_CHA_CLOCKTICKS'/, +cha/event=0x36,umask=0x21,config1=0x4043300000000,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40433'/, +cha/event=0x35,umask=0x21,config1=0x4043300000000,name='UNC_CHA_TOR_INSERTS.IA_MISS.0x40433'/; + +cha/event=0x35,umask=0x21,config1=0x4043200000000,name='UNC_CHA_TOR_INSERTS.IA_MISS.0x40432'/, +cha/event=0x36,umask=0x21,config1=0x4043200000000,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40432'/; + +cha/event=0x35,umask=0x21,config1=0x4043100000000,name='UNC_CHA_TOR_INSERTS.IA_MISS.0x40431'/, +cha/event=0x36,umask=0x21,config1=0x4043100000000,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40431'/; + +cha/event=0x35,umask=0x21,config1=0x12CC023300000000,name='UNC_CHA_TOR_INSERTS.IA_MISS.0x12CC0233'/; + +cha/event=0x35,umask=0x21,config1=0x12D4043300000000,name='UNC_CHA_TOR_INSERTS.IA_MISS.0x12D40433'/; + +cha/event=0x35,umask=0x21,config1=0x12C4003300000000,name='UNC_CHA_TOR_INSERTS.IA_MISS.0x12C40033'/; + +#IO bandwidth +iio/event=0x83,umask=0x04,ch_mask=0x00,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART0'/, +iio/event=0x83,umask=0x04,ch_mask=0x02,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART1'/; + +iio/event=0x83,umask=0x04,ch_mask=0x04,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART2'/, +iio/event=0x83,umask=0x04,ch_mask=0x08,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART3'/; + +iio/event=0x83,umask=0x01,ch_mask=0x00,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART0'/, +iio/event=0x83,umask=0x01,ch_mask=0x02,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART1'/; +iio/event=0x83,umask=0x01,ch_mask=0x04,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART2'/, +iio/event=0x83,umask=0x01,ch_mask=0x08,fc_mask=0x07,name='UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART3'/; + +#UPI related +upi/event=0x2,umask=0x0f,name='UNC_UPI_TxL_FLITS.ALL_DATA'/, +upi/event=0x2,umask=0x97,name='UNC_UPI_TxL_FLITS.NON_DATA'/, +upi/event=0x1,umask=0x0,name='UNC_UPI_CLOCKTICKS'/, +upi/event=0x21,umask=0x0,name='UNC_UPI_L1_POWER_CYCLES'/; + +#power related +power/energy-pkg/, +power/energy-ram/; \ No newline at end of file diff --git a/events/spr_emr.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/spr.txt similarity index 97% rename from events/spr_emr.txt rename to cmd/metrics/resources/events/x86_64/GenuineIntel/spr.txt index 5027aa0..08ed4eb 100644 --- a/events/spr_emr.txt +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/spr.txt @@ -1,8 +1,3 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - # SapphireRapids event list cpu/event=0x51,umask=0x01,period=100003,name='L1D.REPLACEMENT'/, diff --git a/cmd/metrics/resources/events/x86_64/GenuineIntel/spr_nofixedtma.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/spr_nofixedtma.txt new file mode 100644 index 0000000..0053fbe --- /dev/null +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/spr_nofixedtma.txt @@ -0,0 +1,133 @@ +# SapphireRapids event list for platforms that don't have support for the fixed counter +# TMA events, e.g., some AWS VMs. +# Note that there are no more than 10 events per group. On these same platforms, the cpu-cycles fixed +# counter is not supported so a general purpose counter will be used. + +cpu/event=0x51,umask=0x01,period=100003,name='L1D.REPLACEMENT'/, +cpu/event=0x24,umask=0xe4,period=200003,name='L2_RQSTS.ALL_CODE_RD'/, +cpu/event=0xd1,umask=0x01,period=1000003,name='MEM_LOAD_RETIRED.L1_HIT'/, +cpu/event=0x25,umask=0x1f,period=100003,name='L2_LINES_IN.ALL'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0xd1,umask=0x10,period=100021,name='MEM_LOAD_RETIRED.L2_MISS'/, +cpu/event=0x24,umask=0x24,period=200003,name='L2_RQSTS.CODE_RD_MISS'/, +cpu/event=0x11,umask=0x0e,period=100003,name='ITLB_MISSES.WALK_COMPLETED'/, +cpu/event=0x47,umask=0x03,cmask=0x03,period=1000003,name='MEMORY_ACTIVITY.STALLS_L1D_MISS'/, +cpu/event=0xa6,umask=0x40,cmask=0x02,period=1000003,name='EXE_ACTIVITY.BOUND_ON_STORES'/, +cpu/event=0xa6,umask=0x21,cmask=0x05,period=2000003,name='EXE_ACTIVITY.BOUND_ON_LOADS'/, +cpu/event=0xad,umask=0x10,period=1000003,name='INT_MISC.UOP_DROPPING'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x12,umask=0x0e,period=100003,name='DTLB_LOAD_MISSES.WALK_COMPLETED'/, +cpu/event=0x12,umask=0x04,period=100003,name='DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M'/, +cpu/event=0x13,umask=0x0e,period=100003,name='DTLB_STORE_MISSES.WALK_COMPLETED'/, +cpu/event=0xd1,umask=0x02,period=200003,name='MEM_LOAD_RETIRED.L2_HIT'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x47,umask=0x09,cmask=0x09,period=1000003,name='MEMORY_ACTIVITY.STALLS_L3_MISS'/, +cpu/event=0x80,umask=0x04,period=500009,name='ICACHE_DATA.STALLS'/, +cpu/event=0x83,umask=0x04,period=200003,name='ICACHE_TAG.STALLS'/, +cpu-cycles, +ref-cycles, +instructions; + +# events for TMA metrics without fixed counter support (group 1) +cpu/event=0x9c,umask=0x01,name='IDQ_UOPS_NOT_DELIVERED.CORE'/, +cpu/event=0xa4,umask=0x01,name='TOPDOWN.SLOTS_P'/, +cpu/event=0x9c,umask=0x01,name='IDQ_BUBBLES.CYCLES_0_UOPS_DELIV.CORE'/, +cpu/event=0xc2,umask=0x02,name='UOPS_RETIRED.SLOTS'/, +cpu/event=0xae,umask=0x01,name='UOPS_ISSUED.ANY'/, +cpu/event=0x87,umask=0x01,name='DECODE.LCP'/, +cpu/event=0x61,umask=0x02,name='DSB2MITE_SWITCHES.PENALTY_CYCLES'/, +cpu-cycles, +ref-cycles, +instructions; + +# events for TMA metrics without fixed counter support (group 2) +cpu/event=0xa4,umask=0x02,name='TOPDOWN.BACKEND_BOUND_SLOTS'/, +cpu/event=0xa4,umask=0x08,name='TOPDOWN.BR_MISPREDICT_SLOTS'/, +cpu/event=0xa4,umask=0x10,name='TOPDOWN.MEMORY_BOUND_SLOTS'/, +cpu/event=0xc2,umask=0x01,name='UOPS_RETIRED.HEAVY'/, +cpu/event=0xe5,umask=0x03,name='MEM_UOP_RETIRED.ANY'/, +cpu/event=0xc0,umask=0x10,name='INST_RETIRED.MACRO_FUSED'/, +cpu/event=0xc4,umask=0x00,name='BR_INST_RETIRED.ALL_BRANCHES'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x47,umask=0x03,cmask=0x03,period=1000003,name='MEMORY_ACTIVITY.STALLS_L1D_MISS'/, +cpu/event=0x47,umask=0x05,cmask=0x05,period=1000003,name='MEMORY_ACTIVITY.STALLS_L2_MISS'/, +cpu/event=0xb0,umask=0x09,cmask=0x01,period=1000003,name='ARITH.DIV_ACTIVE'/, +cpu/event=0xec,umask=0x02,period=2000003,name='CPU_CLK_UNHALTED.DISTRIBUTED'/, +cpu/event=0xd0,umask=0x21,cmask=0x00,period=1000003,name='MEM_INST_RETIRED.LOCK_LOADS'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x79,umask=0x04,cmask=0x01,period=2000003,name='IDQ.MITE_CYCLES_ANY'/, +cpu/event=0x79,umask=0x04,cmask=0x06,period=2000003,name='IDQ.MITE_CYCLES_OK'/, +cpu/event=0x79,umask=0x08,cmask=0x01,period=2000003,name='IDQ.DSB_CYCLES_ANY'/, +cpu/event=0x79,umask=0x08,cmask=0x06,period=2000003,name='IDQ.DSB_CYCLES_OK'/, +cpu/event=0xec,umask=0x02,period=2000003,name='CPU_CLK_UNHALTED.DISTRIBUTED'/, +cpu/event=0xb7,umask=0x02,period=2000003,name='EXE.AMX_BUSY'/, +cpu-cycles, +ref-cycles, +instructions; + +cpu/event=0x79,umask=0x08,cmask=0x00,period=2000003,name='IDQ.DSB_UOPS'/, +cpu/event=0x79,umask=0x04,period=100003,name='IDQ.MITE_UOPS'/, +cpu/event=0x79,umask=0x20,period=100003,name='IDQ.MS_UOPS'/, +cpu/event=0xa8,umask=0x01,cmask=0x00,period=2000003,name='LSD.UOPS'/, +cpu-cycles:k, +ref-cycles:k, +instructions:k; + +#OCR +cpu/event=0x2a,umask=0x01,offcore_rsp=0x104004477,name='OCR.READS_TO_CORE.LOCAL_DRAM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x730004477,name='OCR.READS_TO_CORE.REMOTE_DRAM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x90002380,name='OCR.HWPF_L3.REMOTE'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x84002380,name='OCR.HWPF_L3.L3_MISS_LOCAL'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x1030004477,name='OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM'/, +cpu/event=0x2a,umask=0x01,offcore_rsp=0x830004477,name='OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD'/; + +#C6 +cstate_core/c6-residency/; +cstate_pkg/c6-residency/; + +#UPI +upi/event=0x02,umask=0x0f,name='UNC_UPI_TxL_FLITS.ALL_DATA'/; + +#CHA (Cache) +cha/event=0x35,umask=0xc80ffe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_CRD'/, +cha/event=0x35,umask=0xc8177e01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE'/, +cha/event=0x36,umask=0xc8177e01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE'/; + +cha/event=0x35,umask=0xC816FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL'/, +cha/event=0x36,umask=0xc816fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL'/, +cha/event=0x35,umask=0xC896FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL'/, +cha/event=0x35,umask=0xC8977E01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE'/; + +cha/event=0x35,umask=0xccd7fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA'/, +cha/event=0x35,umask=0xc817fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD'/, +cha/event=0x35,umask=0xc897fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF'/, +cha/event=0x36,umask=0xC817fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD'/; + +#CHA (IO Bandwidth) +cha/event=0x35,umask=0xc8f3ff04,name='UNC_CHA_TOR_INSERTS.IO_PCIRDCUR'/, +cha/event=0x35,umask=0xCC43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOM'/, +cha/event=0x35,umask=0xCD43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR'/, +cha/event=0x01,umask=0x00,name='UNC_CHA_CLOCKTICKS'/; + +#IMC (memory read/writes) +imc/event=0x05,umask=0xcf,name='UNC_M_CAS_COUNT.RD'/, +imc/event=0x05,umask=0xf0,name='UNC_M_CAS_COUNT.WR'/; + +#power +power/energy-pkg/, +power/energy-ram/; diff --git a/events/srf.txt b/cmd/metrics/resources/events/x86_64/GenuineIntel/srf.txt similarity index 94% rename from events/srf.txt rename to cmd/metrics/resources/events/x86_64/GenuineIntel/srf.txt index 49b3fe1..e9122ec 100644 --- a/events/srf.txt +++ b/cmd/metrics/resources/events/x86_64/GenuineIntel/srf.txt @@ -1,8 +1,3 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - # SierraForest event list cpu-cycles:k, diff --git a/cmd/metrics/resources/metrics/x86_64/AuthenticAMD/bergamo.json b/cmd/metrics/resources/metrics/x86_64/AuthenticAMD/bergamo.json new file mode 100644 index 0000000..7fe32a2 --- /dev/null +++ b/cmd/metrics/resources/metrics/x86_64/AuthenticAMD/bergamo.json @@ -0,0 +1,299 @@ +[ + { + "name": "metric_CPU operating frequency (in GHz)", + "expression": "(([cpu-cycles] / [ls_not_halted_p0_cyc] * [SYSTEM_TSC_FREQ]) / 1000000000)" + }, + { + "name": "metric_CPU utilization %", + "expression": "(100 * [ls_not_halted_p0_cyc]) / [TSC]" + }, + { + "name": "metric_CPU utilization% in kernel mode", + "expression": "(100 * [ls_not_halted_p0_cyc:k]) / [TSC]" + }, + { + "name": "metric_CPI", + "expression": "[cpu-cycles] / [instructions]" + }, + { + "name": "metric_kernel_CPI", + "expression": "[cpu-cycles:k] / [instructions:k]" + }, + { + "name": "metric_IPC", + "expression": "[instructions] / [cpu-cycles]" + }, + { + "name": "metric_giga_instructions_per_sec", + "expression": "[instructions] / 1000000000" + }, + { + "name": "metric_Branch Misprediction Ratio", + "expression": "[ex_ret_brn_misp] / [ex_ret_brn]" + }, + { + "name": "metric_All Data Cache Accesses", + "expression": "[ls_dispatch.any]" + }, + { + "name": "metric_All L2 Cache Accesses", + "expression": "[l2_request_g1.all_no_prefetch] + [l2_pf_hit_l2.all] + [l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_L2 Cache Accesses from L1 Instruction Cache Misses", + "expression": "[l2_request_g1.cacheable_ic_read]" + }, + { + "name": "metric_L2 Cache Accesses from L1 Data Cache Misses", + "expression": "[ic_tag_hit_miss.miss] / [instructions]" + }, + { + "name": "metric_L2 Cache Accesses from L2 Cache Hardware Prefetches", + "expression": "[l2_pf_hit_l2.all] + [l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_All L2 Cache Misses", + "expression": "[l2_cache_req_stat.ic_dc_miss_in_l2] + [l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_L2 Cache Misses from L1 Instruction Cache Misses", + "expression": "[l2_cache_req_stat.ic_fill_miss]" + }, + { + "name": "metric_L2 Cache Misses from L1 Data Cache Misses", + "expression": "[l2_cache_req_stat.ls_rd_blk_c]" + }, + { + "name": "metric_L2 Cache Misses from L2 Cache Hardware Prefetches", + "expression": "[l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_All L2 Cache Hits", + "expression": "[l2_cache_req_stat.ic_dc_hit_in_l2] + [l2_pf_hit_l2.all]" + }, + { + "name": "metric_L2 Cache Hits from L1 Instruction Cache Misses", + "expression": "[l2_cache_req_stat.ic_hit_in_l2]" + }, + { + "name": "metric_L2 Cache Hits from L1 Data Cache Misses", + "expression": "[l2_cache_req_stat.dc_hit_in_l2]" + }, + { + "name": "metric_L2 Cache Hits from L2 Cache Hardware Prefetches", + "expression": "[l2_pf_hit_l2.all]" + }, + { + "name": "metric_L3 Cache Accesses", + "expression": "[l3_lookup_state.all_coherent_accesses_to_l3]" + }, + { + "name": "metric_L3 Cache Misses", + "expression": "[l3_lookup_state.l3_miss]" + }, + { + "name": "metric_L3 Cache Hits", + "expression": "[l3_lookup_state.l3_hit]" + }, + { + "name": "metric_Average L3 Cache Read Miss Latency (in ns)", + "expression": "([l3_xi_sampled_latency.all] * 30) / [l3_xi_sampled_latency_requests.all]" + }, + { + "name": "metric_Op Cache Fetch Miss Ratio", + "expression": "[op_cache_hit_miss.miss] / [op_cache_hit_miss.all]" + }, + { + "name": "metric_Instruction Cache Fetch Miss Ratio", + "expression": "[ic_tag_hit_miss.miss] / [ic_tag_hit_miss.all]" + }, + { + "name": "metric_L1 Data Cache Fills from DRAM or IO in any NUMA node", + "expression": "[ls_any_fills_from_sys.dram_io_all]" + }, + { + "name": "metric_L1 Data Cache Fills from a different NUMA node", + "expression": "[ls_any_fills_from_sys.far_all]" + }, + { + "name": "metric_L1 Data Cache Fills from within the same CCX", + "expression": "[ls_any_fills_from_sys.local_all]" + }, + { + "name": "metric_L1 Data Cache Fills from another CCX cache in any NUMA node", + "expression": "[ls_any_fills_from_sys.remote_cache]" + }, + { + "name": "metric_All L1 Data Cache Fills", + "expression": "[ls_any_fills_from_sys.all]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from local L2", + "expression": "[ls_dmnd_fills_from_sys.local_l2]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from local L3 or different L2 in same CCX", + "expression": "[ls_dmnd_fills_from_sys.local_ccx]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from another CCX cache in the same NUMA node", + "expression": "[ls_dmnd_fills_from_sys.near_cache]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from DRAM or MMIO in the same NUMA node", + "expression": "[ls_dmnd_fills_from_sys.dram_io_near]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from another CCX cache in a different NUMA node", + "expression": "[ls_dmnd_fills_from_sys.far_cache]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from DRAM or MMIO in a different NUMA node", + "expression": "[ls_dmnd_fills_from_sys.dram_io_far]" + }, + { + "name": "metric_Lines written per Write Combining Buffer close", + "expression": "[ls_wcb_close_flush.full_line_64b] / [l2_wcb_req.wcb_close]" + }, + { + "name": "metric_L1 ITLB Misses", + "expression": "[bp_l1_tlb_miss_l2_tlb_hit] + [bp_l1_tlb_miss_l2_tlb_miss.all]" + }, + { + "name": "metric_L2 ITLB Misses and Instruction Page Walks", + "expression": "[bp_l1_tlb_miss_l2_tlb_miss.all]" + }, + { + "name": "metric_L1 DTLB Misses", + "expression": "[ls_l1_d_tlb_miss.all]" + }, + { + "name": "metric_L2 DTLB Misses and Data Page Walks", + "expression": "[ls_l1_d_tlb_miss.all_l2_miss]" + }, + { + "name": "metric_All TLBs Flushed", + "expression": "[ls_tlb_flush.all]" + }, + { + "name": "metric_Macro-ops Dispatched", + "expression": "[de_src_op_disp.all]" + }, + { + "name": "metric_Mixed SSE and AVX Stalls", + "expression": "[fp_disp_faults.sse_avx_all]" + }, + { + "name": "metric_Macro-ops Retired", + "expression": "[ex_ret_ops]" + }, + { + "name": "metric_DRAM read bandwidth for local processor", + "expression": "(64 * ([local_processor_read_data_beats_cs0] + [local_processor_read_data_beats_cs1] + [local_processor_read_data_beats_cs2] + [local_processor_read_data_beats_cs3] + [local_processor_read_data_beats_cs4] + [local_processor_read_data_beats_cs5] + [local_processor_read_data_beats_cs6] + [local_processor_read_data_beats_cs7] + [local_processor_read_data_beats_cs8] + [local_processor_read_data_beats_cs9] + [local_processor_read_data_beats_cs10] + [local_processor_read_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_DRAM write bandwidth for local processor", + "expression": "(64 * ([local_processor_write_data_beats_cs0] + [local_processor_write_data_beats_cs1] + [local_processor_write_data_beats_cs2] + [local_processor_write_data_beats_cs3] + [local_processor_write_data_beats_cs4] + [local_processor_write_data_beats_cs5] + [local_processor_write_data_beats_cs6] + [local_processor_write_data_beats_cs7] + [local_processor_write_data_beats_cs8] + [local_processor_write_data_beats_cs9] + [local_processor_write_data_beats_cs10] + [local_processor_write_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_DRAM read bandwidth for remote processor", + "expression": "(64 * ([remote_processor_read_data_beats_cs0] + [remote_processor_read_data_beats_cs1] + [remote_processor_read_data_beats_cs2] + [remote_processor_read_data_beats_cs3] + [remote_processor_read_data_beats_cs4] + [remote_processor_read_data_beats_cs5] + [remote_processor_read_data_beats_cs6] + [remote_processor_read_data_beats_cs7] + [remote_processor_read_data_beats_cs8] + [remote_processor_read_data_beats_cs9] + [remote_processor_read_data_beats_cs10] + [remote_processor_read_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_DRAM write bandwidth for remote processor", + "expression": "(64 * ([remote_processor_write_data_beats_cs0] + [remote_processor_write_data_beats_cs1] + [remote_processor_write_data_beats_cs2] + [remote_processor_write_data_beats_cs3] + [remote_processor_write_data_beats_cs4] + [remote_processor_write_data_beats_cs5] + [remote_processor_write_data_beats_cs6] + [remote_processor_write_data_beats_cs7] + [remote_processor_write_data_beats_cs8] + [remote_processor_write_data_beats_cs9] + [remote_processor_write_data_beats_cs10] + [remote_processor_write_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_Local socket upstream DMA read bandwidth", + "expression": "(64 * ([local_socket_upstream_read_beats_iom0] + [local_socket_upstream_read_beats_iom1] + [local_socket_upstream_read_beats_iom2] + [local_socket_upstream_read_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Local socket upstream DMA write bandwidth", + "expression": "(64 * ([local_socket_upstream_write_beats_iom0] + [local_socket_upstream_write_beats_iom1] + [local_socket_upstream_write_beats_iom2] + [local_socket_upstream_write_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket upstream DMA read bandwidth", + "expression": "(64 * ([remote_socket_upstream_read_beats_iom0] + [remote_socket_upstream_read_beats_iom1] + [remote_socket_upstream_read_beats_iom2] + [remote_socket_upstream_read_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket upstream DMA write bandwidth", + "expression": "(64 * ([remote_socket_upstream_write_beats_iom0] + [remote_socket_upstream_write_beats_iom1] + [remote_socket_upstream_write_beats_iom2] + [remote_socket_upstream_write_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Local socket inbound bandwidth to the CPU", + "expression": "(32 * ([local_socket_inf0_inbound_data_beats_ccm0] + [local_socket_inf1_inbound_data_beats_ccm0] + [local_socket_inf0_inbound_data_beats_ccm1] + [local_socket_inf1_inbound_data_beats_ccm1] + [local_socket_inf0_inbound_data_beats_ccm2] + [local_socket_inf1_inbound_data_beats_ccm2] + [local_socket_inf0_inbound_data_beats_ccm3] + [local_socket_inf1_inbound_data_beats_ccm3] + [local_socket_inf0_inbound_data_beats_ccm4] + [local_socket_inf1_inbound_data_beats_ccm4] + [local_socket_inf0_inbound_data_beats_ccm5] + [local_socket_inf1_inbound_data_beats_ccm5] + [local_socket_inf0_inbound_data_beats_ccm6] + [local_socket_inf1_inbound_data_beats_ccm6] + [local_socket_inf0_inbound_data_beats_ccm7] + [local_socket_inf1_inbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Local socket outbound bandwidth from the CPU", + "expression": "(64 * ([local_socket_inf0_outbound_data_beats_ccm0] + [local_socket_inf1_outbound_data_beats_ccm0] + [local_socket_inf0_outbound_data_beats_ccm1] + [local_socket_inf1_outbound_data_beats_ccm1] + [local_socket_inf0_outbound_data_beats_ccm2] + [local_socket_inf1_outbound_data_beats_ccm2] + [local_socket_inf0_outbound_data_beats_ccm3] + [local_socket_inf1_outbound_data_beats_ccm3] + [local_socket_inf0_outbound_data_beats_ccm4] + [local_socket_inf1_outbound_data_beats_ccm4] + [local_socket_inf0_outbound_data_beats_ccm5] + [local_socket_inf1_outbound_data_beats_ccm5] + [local_socket_inf0_outbound_data_beats_ccm6] + [local_socket_inf1_outbound_data_beats_ccm6] + [local_socket_inf0_outbound_data_beats_ccm7] + [local_socket_inf1_outbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket inbound bandwidth to the CPU", + "expression": "(32 * ([remote_socket_inf0_inbound_data_beats_ccm0] + [remote_socket_inf1_inbound_data_beats_ccm0] + [remote_socket_inf0_inbound_data_beats_ccm1] + [remote_socket_inf1_inbound_data_beats_ccm1] + [remote_socket_inf0_inbound_data_beats_ccm2] + [remote_socket_inf1_inbound_data_beats_ccm2] + [remote_socket_inf0_inbound_data_beats_ccm3] + [remote_socket_inf1_inbound_data_beats_ccm3] + [remote_socket_inf0_inbound_data_beats_ccm4] + [remote_socket_inf1_inbound_data_beats_ccm4] + [remote_socket_inf0_inbound_data_beats_ccm5] + [remote_socket_inf1_inbound_data_beats_ccm5] + [remote_socket_inf0_inbound_data_beats_ccm6] + [remote_socket_inf1_inbound_data_beats_ccm6] + [remote_socket_inf0_inbound_data_beats_ccm7] + [remote_socket_inf1_inbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket outbound bandwidth from the CPU", + "expression": "(64 * ([remote_socket_inf0_outbound_data_beats_ccm0] + [remote_socket_inf1_outbound_data_beats_ccm0] + [remote_socket_inf0_outbound_data_beats_ccm1] + [remote_socket_inf1_outbound_data_beats_ccm1] + [remote_socket_inf0_outbound_data_beats_ccm2] + [remote_socket_inf1_outbound_data_beats_ccm2] + [remote_socket_inf0_outbound_data_beats_ccm3] + [remote_socket_inf1_outbound_data_beats_ccm3] + [remote_socket_inf0_outbound_data_beats_ccm4] + [remote_socket_inf1_outbound_data_beats_ccm4] + [remote_socket_inf0_outbound_data_beats_ccm5] + [remote_socket_inf1_outbound_data_beats_ccm5] + [remote_socket_inf0_outbound_data_beats_ccm6] + [remote_socket_inf1_outbound_data_beats_ccm6] + [remote_socket_inf0_outbound_data_beats_ccm7] + [remote_socket_inf1_outbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Outbound bandwidth from all links", + "expression": "(64 * ([local_socket_outbound_data_beats_link0] + [local_socket_outbound_data_beats_link1] + [local_socket_outbound_data_beats_link2] + [local_socket_outbound_data_beats_link3] + [local_socket_outbound_data_beats_link4] + [local_socket_outbound_data_beats_link5] + [local_socket_outbound_data_beats_link6] + [local_socket_outbound_data_beats_link7]) / 1000000) / 1" + }, + { + "name": "metric_Pipeline Utilization - Frontend Bound (%)", + "expression": "([de_no_dispatch_per_slot.no_ops_from_frontend] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Frontend Bound - Latency (%)", + "expression": "((6 * [de_no_dispatch_per_cycle.no_ops_from_frontend]) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Frontend Bound - Bandwidth (%)", + "expression": "((([de_no_dispatch_per_slot.no_ops_from_frontend] / 6) - ([de_no_dispatch_per_cycle.no_ops_from_frontend])) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Bad Speculation (%)", + "expression": "(([de_src_op_disp.all] - [ex_ret_ops]) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Bad Speculation - Mispredicts (%)", + "expression": "((([de_src_op_disp.all] - [ex_ret_ops]) * ([ex_ret_brn_misp] / ([ex_ret_brn_misp] + [resyncs_or_nc_redirects]))) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Bad Speculation - Pipeline Restarts (%)", + "expression": "((([de_src_op_disp.all] - [ex_ret_ops]) * ([resyncs_or_nc_redirects] / ([ex_ret_brn_misp] + [resyncs_or_nc_redirects]))) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Backend Bound (%)", + "expression": "([de_no_dispatch_per_slot.backend_stalls] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Backend Bound - Memory (%)", + "expression": "(([de_no_dispatch_per_slot.backend_stalls] * ([ex_no_retire.load_not_complete] / [ex_no_retire.not_complete])) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Backend Bound - CPU (%)", + "expression": "(([de_no_dispatch_per_slot.backend_stalls] * (1 - ([ex_no_retire.load_not_complete] / [ex_no_retire.not_complete]))) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - SMT Contention (%)", + "expression": "([de_no_dispatch_per_slot.smt_contention] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Retiring (%)", + "expression": "([ex_ret_ops] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Retiring - Fastpath (%)", + "expression": "(([ex_ret_ops] - [ex_ret_ucode_ops]) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Retiring - Microcode (%)", + "expression": "([ex_ret_ucode_ops] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_package power (watts)", + "expression": "[power/energy-pkg/]", + "origin": "perfspect" + } +] \ No newline at end of file diff --git a/cmd/metrics/resources/metrics/x86_64/AuthenticAMD/genoa.json b/cmd/metrics/resources/metrics/x86_64/AuthenticAMD/genoa.json new file mode 100644 index 0000000..bdcefd9 --- /dev/null +++ b/cmd/metrics/resources/metrics/x86_64/AuthenticAMD/genoa.json @@ -0,0 +1,299 @@ +[ + { + "name": "metric_CPU operating frequency (in GHz)", + "expression": "(([cpu-cycles] / [ls_not_halted_p0_cyc] * [SYSTEM_TSC_FREQ]) / 1000000000)" + }, + { + "name": "metric_CPU utilization %", + "expression": "(100 * [ls_not_halted_p0_cyc]) / [TSC]" + }, + { + "name": "metric_CPU utilization% in kernel mode", + "expression": "(100 * [ls_not_halted_p0_cyc:k]) / [TSC]" + }, + { + "name": "metric_CPI", + "expression": "[cpu-cycles] / [instructions]" + }, + { + "name": "metric_kernel_CPI", + "expression": "[cpu-cycles:k] / [instructions:k]" + }, + { + "name": "metric_IPC", + "expression": "[instructions] / [cpu-cycles]" + }, + { + "name": "metric_giga_instructions_per_sec", + "expression": "[instructions] / 1000000000" + }, + { + "name": "metric_Branch Misprediction Ratio", + "expression": "[ex_ret_brn_misp] / [ex_ret_brn]" + }, + { + "name": "metric_All Data Cache Accesses", + "expression": "[ls_dispatch.any]" + }, + { + "name": "metric_All L2 Cache Accesses", + "expression": "[l2_request_g1.all_no_prefetch] + [l2_pf_hit_l2.all] + [l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_L2 Cache Accesses from L1 Instruction Cache Misses", + "expression": "[l2_request_g1.cacheable_ic_read]" + }, + { + "name": "metric_L2 Cache Accesses from L1 Data Cache Misses", + "expression": "[ic_tag_hit_miss.miss] / [instructions]" + }, + { + "name": "metric_L2 Cache Accesses from L2 Cache Hardware Prefetches", + "expression": "[l2_pf_hit_l2.all] + [l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_All L2 Cache Misses", + "expression": "[l2_cache_req_stat.ic_dc_miss_in_l2] + [l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_L2 Cache Misses from L1 Instruction Cache Misses", + "expression": "[l2_cache_req_stat.ic_fill_miss]" + }, + { + "name": "metric_L2 Cache Misses from L1 Data Cache Misses", + "expression": "[l2_cache_req_stat.ls_rd_blk_c]" + }, + { + "name": "metric_L2 Cache Misses from L2 Cache Hardware Prefetches", + "expression": "[l2_pf_miss_l2_hit_l3.all] + [l2_pf_miss_l2_l3.all]" + }, + { + "name": "metric_All L2 Cache Hits", + "expression": "[l2_cache_req_stat.ic_dc_hit_in_l2] + [l2_pf_hit_l2.all]" + }, + { + "name": "metric_L2 Cache Hits from L1 Instruction Cache Misses", + "expression": "[l2_cache_req_stat.ic_hit_in_l2]" + }, + { + "name": "metric_L2 Cache Hits from L1 Data Cache Misses", + "expression": "[l2_cache_req_stat.dc_hit_in_l2]" + }, + { + "name": "metric_L2 Cache Hits from L2 Cache Hardware Prefetches", + "expression": "[l2_pf_hit_l2.all]" + }, + { + "name": "metric_L3 Cache Accesses", + "expression": "[l3_lookup_state.all_coherent_accesses_to_l3]" + }, + { + "name": "metric_L3 Cache Misses", + "expression": "[l3_lookup_state.l3_miss]" + }, + { + "name": "metric_L3 Cache Hits", + "expression": "[l3_lookup_state.l3_hit]" + }, + { + "name": "metric_Average L3 Cache Read Miss Latency (in ns)", + "expression": "([l3_xi_sampled_latency.all] * 10) / [l3_xi_sampled_latency_requests.all]" + }, + { + "name": "metric_Op Cache Fetch Miss Ratio", + "expression": "[op_cache_hit_miss.miss] / [op_cache_hit_miss.all]" + }, + { + "name": "metric_Instruction Cache Fetch Miss Ratio", + "expression": "[ic_tag_hit_miss.miss] / [ic_tag_hit_miss.all]" + }, + { + "name": "metric_L1 Data Cache Fills from DRAM or IO in any NUMA node", + "expression": "[ls_any_fills_from_sys.dram_io_all]" + }, + { + "name": "metric_L1 Data Cache Fills from a different NUMA node", + "expression": "[ls_any_fills_from_sys.far_all]" + }, + { + "name": "metric_L1 Data Cache Fills from within the same CCX", + "expression": "[ls_any_fills_from_sys.local_all]" + }, + { + "name": "metric_L1 Data Cache Fills from another CCX cache in any NUMA node", + "expression": "[ls_any_fills_from_sys.remote_cache]" + }, + { + "name": "metric_All L1 Data Cache Fills", + "expression": "[ls_any_fills_from_sys.all]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from local L2", + "expression": "[ls_dmnd_fills_from_sys.local_l2]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from local L3 or different L2 in same CCX", + "expression": "[ls_dmnd_fills_from_sys.local_ccx]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from another CCX cache in the same NUMA node", + "expression": "[ls_dmnd_fills_from_sys.near_cache]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from DRAM or MMIO in the same NUMA node", + "expression": "[ls_dmnd_fills_from_sys.dram_io_near]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from another CCX cache in a different NUMA node", + "expression": "[ls_dmnd_fills_from_sys.far_cache]" + }, + { + "name": "metric_Demand L1 Data Cache Fills from DRAM or MMIO in a different NUMA node", + "expression": "[ls_dmnd_fills_from_sys.dram_io_far]" + }, + { + "name": "metric_Lines written per Write Combining Buffer close", + "expression": "[ls_wcb_close_flush.full_line_64b] / [l2_wcb_req.wcb_close]" + }, + { + "name": "metric_L1 ITLB Misses", + "expression": "[bp_l1_tlb_miss_l2_tlb_hit] + [bp_l1_tlb_miss_l2_tlb_miss.all]" + }, + { + "name": "metric_L2 ITLB Misses and Instruction Page Walks", + "expression": "[bp_l1_tlb_miss_l2_tlb_miss.all]" + }, + { + "name": "metric_L1 DTLB Misses", + "expression": "[ls_l1_d_tlb_miss.all]" + }, + { + "name": "metric_L2 DTLB Misses and Data Page Walks", + "expression": "[ls_l1_d_tlb_miss.all_l2_miss]" + }, + { + "name": "metric_All TLBs Flushed", + "expression": "[ls_tlb_flush.all]" + }, + { + "name": "metric_Macro-ops Dispatched", + "expression": "[de_src_op_disp.all]" + }, + { + "name": "metric_Mixed SSE and AVX Stalls", + "expression": "[fp_disp_faults.sse_avx_all]" + }, + { + "name": "metric_Macro-ops Retired", + "expression": "[ex_ret_ops]" + }, + { + "name": "metric_DRAM read bandwidth for local processor", + "expression": "(64 * ([local_processor_read_data_beats_cs0] + [local_processor_read_data_beats_cs1] + [local_processor_read_data_beats_cs2] + [local_processor_read_data_beats_cs3] + [local_processor_read_data_beats_cs4] + [local_processor_read_data_beats_cs5] + [local_processor_read_data_beats_cs6] + [local_processor_read_data_beats_cs7] + [local_processor_read_data_beats_cs8] + [local_processor_read_data_beats_cs9] + [local_processor_read_data_beats_cs10] + [local_processor_read_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_DRAM write bandwidth for local processor", + "expression": "(64 * ([local_processor_write_data_beats_cs0] + [local_processor_write_data_beats_cs1] + [local_processor_write_data_beats_cs2] + [local_processor_write_data_beats_cs3] + [local_processor_write_data_beats_cs4] + [local_processor_write_data_beats_cs5] + [local_processor_write_data_beats_cs6] + [local_processor_write_data_beats_cs7] + [local_processor_write_data_beats_cs8] + [local_processor_write_data_beats_cs9] + [local_processor_write_data_beats_cs10] + [local_processor_write_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_DRAM read bandwidth for remote processor", + "expression": "(64 * ([remote_processor_read_data_beats_cs0] + [remote_processor_read_data_beats_cs1] + [remote_processor_read_data_beats_cs2] + [remote_processor_read_data_beats_cs3] + [remote_processor_read_data_beats_cs4] + [remote_processor_read_data_beats_cs5] + [remote_processor_read_data_beats_cs6] + [remote_processor_read_data_beats_cs7] + [remote_processor_read_data_beats_cs8] + [remote_processor_read_data_beats_cs9] + [remote_processor_read_data_beats_cs10] + [remote_processor_read_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_DRAM write bandwidth for remote processor", + "expression": "(64 * ([remote_processor_write_data_beats_cs0] + [remote_processor_write_data_beats_cs1] + [remote_processor_write_data_beats_cs2] + [remote_processor_write_data_beats_cs3] + [remote_processor_write_data_beats_cs4] + [remote_processor_write_data_beats_cs5] + [remote_processor_write_data_beats_cs6] + [remote_processor_write_data_beats_cs7] + [remote_processor_write_data_beats_cs8] + [remote_processor_write_data_beats_cs9] + [remote_processor_write_data_beats_cs10] + [remote_processor_write_data_beats_cs11]) / 1000000) / 1" + }, + { + "name": "metric_Local socket upstream DMA read bandwidth", + "expression": "(64 * ([local_socket_upstream_read_beats_iom0] + [local_socket_upstream_read_beats_iom1] + [local_socket_upstream_read_beats_iom2] + [local_socket_upstream_read_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Local socket upstream DMA write bandwidth", + "expression": "(64 * ([local_socket_upstream_write_beats_iom0] + [local_socket_upstream_write_beats_iom1] + [local_socket_upstream_write_beats_iom2] + [local_socket_upstream_write_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket upstream DMA read bandwidth", + "expression": "(64 * ([remote_socket_upstream_read_beats_iom0] + [remote_socket_upstream_read_beats_iom1] + [remote_socket_upstream_read_beats_iom2] + [remote_socket_upstream_read_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket upstream DMA write bandwidth", + "expression": "(64 * ([remote_socket_upstream_write_beats_iom0] + [remote_socket_upstream_write_beats_iom1] + [remote_socket_upstream_write_beats_iom2] + [remote_socket_upstream_write_beats_iom3]) / 1000000) / 1" + }, + { + "name": "metric_Local socket inbound bandwidth to the CPU", + "expression": "(32 * ([local_socket_inf0_inbound_data_beats_ccm0] + [local_socket_inf1_inbound_data_beats_ccm0] + [local_socket_inf0_inbound_data_beats_ccm1] + [local_socket_inf1_inbound_data_beats_ccm1] + [local_socket_inf0_inbound_data_beats_ccm2] + [local_socket_inf1_inbound_data_beats_ccm2] + [local_socket_inf0_inbound_data_beats_ccm3] + [local_socket_inf1_inbound_data_beats_ccm3] + [local_socket_inf0_inbound_data_beats_ccm4] + [local_socket_inf1_inbound_data_beats_ccm4] + [local_socket_inf0_inbound_data_beats_ccm5] + [local_socket_inf1_inbound_data_beats_ccm5] + [local_socket_inf0_inbound_data_beats_ccm6] + [local_socket_inf1_inbound_data_beats_ccm6] + [local_socket_inf0_inbound_data_beats_ccm7] + [local_socket_inf1_inbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Local socket outbound bandwidth from the CPU", + "expression": "(64 * ([local_socket_inf0_outbound_data_beats_ccm0] + [local_socket_inf1_outbound_data_beats_ccm0] + [local_socket_inf0_outbound_data_beats_ccm1] + [local_socket_inf1_outbound_data_beats_ccm1] + [local_socket_inf0_outbound_data_beats_ccm2] + [local_socket_inf1_outbound_data_beats_ccm2] + [local_socket_inf0_outbound_data_beats_ccm3] + [local_socket_inf1_outbound_data_beats_ccm3] + [local_socket_inf0_outbound_data_beats_ccm4] + [local_socket_inf1_outbound_data_beats_ccm4] + [local_socket_inf0_outbound_data_beats_ccm5] + [local_socket_inf1_outbound_data_beats_ccm5] + [local_socket_inf0_outbound_data_beats_ccm6] + [local_socket_inf1_outbound_data_beats_ccm6] + [local_socket_inf0_outbound_data_beats_ccm7] + [local_socket_inf1_outbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket inbound bandwidth to the CPU", + "expression": "(32 * ([remote_socket_inf0_inbound_data_beats_ccm0] + [remote_socket_inf1_inbound_data_beats_ccm0] + [remote_socket_inf0_inbound_data_beats_ccm1] + [remote_socket_inf1_inbound_data_beats_ccm1] + [remote_socket_inf0_inbound_data_beats_ccm2] + [remote_socket_inf1_inbound_data_beats_ccm2] + [remote_socket_inf0_inbound_data_beats_ccm3] + [remote_socket_inf1_inbound_data_beats_ccm3] + [remote_socket_inf0_inbound_data_beats_ccm4] + [remote_socket_inf1_inbound_data_beats_ccm4] + [remote_socket_inf0_inbound_data_beats_ccm5] + [remote_socket_inf1_inbound_data_beats_ccm5] + [remote_socket_inf0_inbound_data_beats_ccm6] + [remote_socket_inf1_inbound_data_beats_ccm6] + [remote_socket_inf0_inbound_data_beats_ccm7] + [remote_socket_inf1_inbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Remote socket outbound bandwidth from the CPU", + "expression": "(64 * ([remote_socket_inf0_outbound_data_beats_ccm0] + [remote_socket_inf1_outbound_data_beats_ccm0] + [remote_socket_inf0_outbound_data_beats_ccm1] + [remote_socket_inf1_outbound_data_beats_ccm1] + [remote_socket_inf0_outbound_data_beats_ccm2] + [remote_socket_inf1_outbound_data_beats_ccm2] + [remote_socket_inf0_outbound_data_beats_ccm3] + [remote_socket_inf1_outbound_data_beats_ccm3] + [remote_socket_inf0_outbound_data_beats_ccm4] + [remote_socket_inf1_outbound_data_beats_ccm4] + [remote_socket_inf0_outbound_data_beats_ccm5] + [remote_socket_inf1_outbound_data_beats_ccm5] + [remote_socket_inf0_outbound_data_beats_ccm6] + [remote_socket_inf1_outbound_data_beats_ccm6] + [remote_socket_inf0_outbound_data_beats_ccm7] + [remote_socket_inf1_outbound_data_beats_ccm7]) / 1000000) / 1" + }, + { + "name": "metric_Outbound bandwidth from all links", + "expression": "(64 * ([local_socket_outbound_data_beats_link0] + [local_socket_outbound_data_beats_link1] + [local_socket_outbound_data_beats_link2] + [local_socket_outbound_data_beats_link3] + [local_socket_outbound_data_beats_link4] + [local_socket_outbound_data_beats_link5] + [local_socket_outbound_data_beats_link6] + [local_socket_outbound_data_beats_link7]) / 1000000) / 1" + }, + { + "name": "metric_Pipeline Utilization - Frontend Bound (%)", + "expression": "([de_no_dispatch_per_slot.no_ops_from_frontend] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Frontend Bound - Latency (%)", + "expression": "((6 * [de_no_dispatch_per_cycle.no_ops_from_frontend]) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Frontend Bound - Bandwidth (%)", + "expression": "((([de_no_dispatch_per_slot.no_ops_from_frontend] / 6) - ([de_no_dispatch_per_cycle.no_ops_from_frontend])) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Bad Speculation (%)", + "expression": "(([de_src_op_disp.all] - [ex_ret_ops]) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Bad Speculation - Mispredicts (%)", + "expression": "((([de_src_op_disp.all] - [ex_ret_ops]) * ([ex_ret_brn_misp] / ([ex_ret_brn_misp] + [resyncs_or_nc_redirects]))) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Bad Speculation - Pipeline Restarts (%)", + "expression": "((([de_src_op_disp.all] - [ex_ret_ops]) * ([resyncs_or_nc_redirects] / ([ex_ret_brn_misp] + [resyncs_or_nc_redirects]))) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Backend Bound (%)", + "expression": "([de_no_dispatch_per_slot.backend_stalls] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Backend Bound - Memory (%)", + "expression": "(([de_no_dispatch_per_slot.backend_stalls] * ([ex_no_retire.load_not_complete] / [ex_no_retire.not_complete])) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Backend Bound - CPU (%)", + "expression": "(([de_no_dispatch_per_slot.backend_stalls] * (1 - ([ex_no_retire.load_not_complete] / [ex_no_retire.not_complete]))) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - SMT Contention (%)", + "expression": "([de_no_dispatch_per_slot.smt_contention] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Retiring (%)", + "expression": "([ex_ret_ops] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Retiring - Fastpath (%)", + "expression": "(([ex_ret_ops] - [ex_ret_ucode_ops]) / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_Pipeline Utilization - Retiring - Microcode (%)", + "expression": "([ex_ret_ucode_ops] / (6 * [ls_not_halted_cyc])) * 100" + }, + { + "name": "metric_package power (watts)", + "expression": "[power/energy-pkg/]", + "origin": "perfspect" + } +] \ No newline at end of file diff --git a/events/metric_bdx.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/bdx.json similarity index 97% rename from events/metric_bdx.json rename to cmd/metrics/resources/metrics/x86_64/GenuineIntel/bdx.json index 7e46419..cef23e6 100644 --- a/events/metric_bdx.json +++ b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/bdx.json @@ -346,22 +346,22 @@ }, { "name": "metric_TMA_......Ports_Utilized_0(%)", - "expression": "100 * (([UOPS_EXECUTED.CORE_i1_c1] / [const_thread_count]) if ([const_thread_count] > 1) else ([RS_EVENTS.EMPTY_CYCLES] if ([CYCLE_ACTIVITY.STALLS_TOTAL] - ([IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE] / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])) ) > 0.1 else 0)) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count]) ", + "expression": "100 * (([UOPS_EXECUTED.CORE_i1_c1] / [CONST_THREAD_COUNT]) if ([CONST_THREAD_COUNT] > 1) else ([RS_EVENTS.EMPTY_CYCLES] if ([CYCLE_ACTIVITY.STALLS_TOTAL] - ([IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE] / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])) ) > 0.1 else 0)) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT]) ", "origin": "perfspect" }, { "name": "metric_TMA_......Ports_Utilized_1(%)", - "expression": "100 * (([UOPS_EXECUTED.CORE_c1] - [UOPS_EXECUTED.CORE_c2]) / [const_thread_count]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])", + "expression": "100 * (([UOPS_EXECUTED.CORE_c1] - [UOPS_EXECUTED.CORE_c2]) / [CONST_THREAD_COUNT]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", "origin": "perfspect" }, { "name": "metric_TMA_......Ports_Utilized_2(%)", - "expression": "100 * (([UOPS_EXECUTED.CORE_c2] - [UOPS_EXECUTED.CORE_c3]) / [const_thread_count]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])", + "expression": "100 * (([UOPS_EXECUTED.CORE_c2] - [UOPS_EXECUTED.CORE_c3]) / [CONST_THREAD_COUNT]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", "origin": "perfspect" }, { "name": "metric_TMA_......Ports_Utilized_3m(%)", - "expression": "100 * ([UOPS_EXECUTED.CORE_c3] / [const_thread_count]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])", + "expression": "100 * ([UOPS_EXECUTED.CORE_c3] / [CONST_THREAD_COUNT]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", "origin": "perfspect" }, { @@ -390,7 +390,7 @@ }, { "name": "metric_TMA_..Microcode_Sequencer(%)", - "expression": "100 * (([UOPS_RETIRED.RETIRE_SLOTS] / [UOPS_ISSUED.ANY]) * [IDQ.MS_UOPS] )/ (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count]))", + "expression": "100 * (([UOPS_RETIRED.RETIRE_SLOTS] / [UOPS_ISSUED.ANY]) * [IDQ.MS_UOPS] )/ (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT]))", "origin": "perfspect" } ] \ No newline at end of file diff --git a/events/metric_skx_clx.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/clx.json similarity index 97% rename from events/metric_skx_clx.json rename to cmd/metrics/resources/metrics/x86_64/GenuineIntel/clx.json index 9974e96..f167036 100644 --- a/events/metric_skx_clx.json +++ b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/clx.json @@ -202,7 +202,7 @@ }, { "name": "metric_UPI Transmit utilization_% (includes control)", - "expression": "100 * (([UNC_UPI_TxL_FLITS.ALL_DATA] + [UNC_UPI_TxL_FLITS.NON_DATA]) / 3) / ((((([SYSTEM_TSC_FREQ] / ([CHAS_PER_SOCKET] * [const_thread_count])) / (([SYSTEM_TSC_FREQ] / ([CHAS_PER_SOCKET] * [const_thread_count])) - [cstate_pkg/c6-residency/])) * ([UNC_UPI_CLOCKTICKS] - [UNC_UPI_L1_POWER_CYCLES])) * 5 / 6))", + "expression": "100 * (([UNC_UPI_TxL_FLITS.ALL_DATA] + [UNC_UPI_TxL_FLITS.NON_DATA]) / 3) / ((((([SYSTEM_TSC_FREQ] / ([CHAS_PER_SOCKET] * [CONST_THREAD_COUNT])) / (([SYSTEM_TSC_FREQ] / ([CHAS_PER_SOCKET] * [CONST_THREAD_COUNT])) - [cstate_pkg/c6-residency/])) * ([UNC_UPI_CLOCKTICKS] - [UNC_UPI_L1_POWER_CYCLES])) * 5 / 6))", "origin": "perfspect" }, { @@ -284,12 +284,12 @@ }, { "name": "metric_TMA_Info_cycles_both_threads_active(%)", - "expression": "100 * ( (1 - ([CPU_CLK_THREAD_UNHALTED.ONE_THREAD_ACTIVE] / ([CPU_CLK_THREAD_UNHALTED.REF_XCLK_ANY] / 2)) ) if [const_thread_count] > 1 else 0)", + "expression": "100 * ( (1 - ([CPU_CLK_THREAD_UNHALTED.ONE_THREAD_ACTIVE] / ([CPU_CLK_THREAD_UNHALTED.REF_XCLK_ANY] / 2)) ) if [CONST_THREAD_COUNT] > 1 else 0)", "origin": "perfspect" }, { "name": "metric_TMA_Info_CoreIPC", - "expression": "[instructions] / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])", + "expression": "[instructions] / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", "origin": "perfspect" }, { @@ -298,7 +298,7 @@ }, { "name": "metric_TMA_..Frontend_Latency(%)", - "expression": "100 * [IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE] / ([CPU_CLK_UNHALTED.THREAD_ANY] /[const_thread_count])", + "expression": "100 * [IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE] / ([CPU_CLK_UNHALTED.THREAD_ANY] /[CONST_THREAD_COUNT])", "origin": "perfspect" }, { @@ -328,7 +328,7 @@ }, { "name": "metric_TMA_..Frontend_Bandwidth(%)", - "expression": "100 * ([IDQ_UOPS_NOT_DELIVERED.CORE] - 4 * [IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE]) / (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count]))", + "expression": "100 * ([IDQ_UOPS_NOT_DELIVERED.CORE] - 4 * [IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE]) / (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT]))", "origin": "perfspect" }, { @@ -416,17 +416,17 @@ }, { "name": "metric_TMA_......Ports_Utilized_0(%)", - "expression": "100 * (([UOPS_EXECUTED.CORE_CYCLES_NONE] / 2) if ([const_thread_count] > 1) else [EXE_ACTIVITY.EXE_BOUND_0_PORTS]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])", + "expression": "100 * (([UOPS_EXECUTED.CORE_CYCLES_NONE] / 2) if ([CONST_THREAD_COUNT] > 1) else [EXE_ACTIVITY.EXE_BOUND_0_PORTS]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", "origin": "perfspect" }, { "name": "metric_TMA_......Ports_Utilized_1(%)", - "expression": "100 * ((([UOPS_EXECUTED.CORE_CYCLES_GE_1] - [UOPS_EXECUTED.CORE_CYCLES_GE_2]) / 2) if ([const_thread_count] > 1) else [EXE_ACTIVITY.1_PORTS_UTIL]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])", + "expression": "100 * ((([UOPS_EXECUTED.CORE_CYCLES_GE_1] - [UOPS_EXECUTED.CORE_CYCLES_GE_2]) / 2) if ([CONST_THREAD_COUNT] > 1) else [EXE_ACTIVITY.1_PORTS_UTIL]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", "origin": "perfspect" }, { "name": "metric_TMA_......Ports_Utilized_2(%)", - "expression": "100 * ((([UOPS_EXECUTED.CORE_CYCLES_GE_2] - [UOPS_EXECUTED.CORE_CYCLES_GE_3]) / 2) if ([const_thread_count] > 1) else [EXE_ACTIVITY.2_PORTS_UTIL]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])", + "expression": "100 * ((([UOPS_EXECUTED.CORE_CYCLES_GE_2] - [UOPS_EXECUTED.CORE_CYCLES_GE_3]) / 2) if ([CONST_THREAD_COUNT] > 1) else [EXE_ACTIVITY.2_PORTS_UTIL]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", "origin": "perfspect" }, { @@ -460,7 +460,7 @@ }, { "name": "metric_TMA_..Microcode_Sequencer(%)", - "expression": "100 * (([UOPS_RETIRED.RETIRE_SLOTS] / [UOPS_ISSUED.ANY]) * [IDQ.MS_UOPS] / (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [const_thread_count])))", + "expression": "100 * (([UOPS_RETIRED.RETIRE_SLOTS] / [UOPS_ISSUED.ANY]) * [IDQ.MS_UOPS] / (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])))", "origin": "perfspect" } ] \ No newline at end of file diff --git a/events/metric_spr_emr.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/emr.json similarity index 100% rename from events/metric_spr_emr.json rename to cmd/metrics/resources/metrics/x86_64/GenuineIntel/emr.json diff --git a/events/metric_spr_emr_nofixedtma.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/emr_nofixedtma.json similarity index 100% rename from events/metric_spr_emr_nofixedtma.json rename to cmd/metrics/resources/metrics/x86_64/GenuineIntel/emr_nofixedtma.json diff --git a/cmd/metrics/resources/metrics/x86_64/GenuineIntel/gnr.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/gnr.json new file mode 100644 index 0000000..e7b1b29 --- /dev/null +++ b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/gnr.json @@ -0,0 +1,374 @@ +[ + { + "name": "metric_CPU operating frequency (in GHz)", + "expression": "([cpu-cycles] / [ref-cycles] * [SYSTEM_TSC_FREQ]) / 1000000000" + }, + { + "name": "metric_CPU utilization %", + "expression": "100 * [ref-cycles] / [TSC]" + }, + { + "name": "metric_CPU utilization% in kernel mode", + "expression": "100 * [ref-cycles:k] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_CPI", + "name-txn": "metric_cycles per txn", + "expression": "[cpu-cycles] / [instructions]", + "expression-txn": "[cpu-cycles] / [TXN]" + }, + { + "name": "metric_kernel_CPI", + "name-txn": "metric_kernel_cycles per txn", + "expression": "[cpu-cycles:k] / [instructions:k]", + "expression-txn": "[cpu-cycles:k] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_IPC", + "name-txn": "metric_txn per cycle", + "expression": "[instructions] / [cpu-cycles]", + "expression-txn": "[TXN] / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_giga_instructions_per_sec", + "expression": "[instructions] / 1000000000", + "origin": "perfspect" + }, + { + "name": "metric_locks retired per instr", + "name-txn": "metric_locks retired per txn", + "expression": "[MEM_INST_RETIRED.LOCK_LOADS] / [instructions]", + "expression-txn": "[MEM_INST_RETIRED.LOCK_LOADS] / [TXN]", + "origin": "perfmon website" + }, + { + "name": "metric_L1D MPI (includes data+rfo w/ prefetches)", + "name-txn": "metric_L1D misses per txn (includes data+rfo w/ prefetches)", + "expression": "[L1D.REPLACEMENT] / [instructions]", + "expression-txn": "[L1D.REPLACEMENT] / [TXN]" + }, + { + "name": "metric_L1D demand data read hits per instr", + "name-txn": "metric_L1D demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L1_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L1_HIT] / [TXN]" + }, + { + "name": "metric_L1-I code read misses (w/ prefetches) per instr", + "name-txn": "metric_L1I code read misses (includes prefetches) per txn", + "expression": "[L2_RQSTS.ALL_CODE_RD] / [instructions]", + "expression-txn": "[L2_RQSTS.ALL_CODE_RD] / [TXN]" + }, + { + "name": "metric_L2 demand data read hits per instr", + "name-txn": "metric_L2 demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L2_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L2_HIT] / [TXN]" + }, + { + "name": "metric_L2 MPI (includes code+data+rfo w/ prefetches)", + "name-txn": "metric_L2 misses per txn (includes code+data+rfo w/ prefetches)", + "expression": "[L2_LINES_IN.ALL] / [instructions]", + "expression-txn": "[L2_LINES_IN.ALL] / [TXN]" + }, + { + "name": "metric_L2 demand data read MPI", + "name-txn": "metric_L2 demand data read misses per txn", + "expression": "[MEM_LOAD_RETIRED.L2_MISS] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L2_MISS] / [TXN]" + }, + { + "name": "metric_L2 demand code MPI", + "name-txn": "metric_L2 demand code misses per txn", + "expression": "[L2_RQSTS.CODE_RD_MISS] / [instructions]", + "expression-txn": "[L2_RQSTS.CODE_RD_MISS] / [TXN]" + }, + { + "name": "metric_LLC code read MPI (demand+prefetch)", + "name-txn": "metric_LLC code read (demand+prefetch) misses per txn", + "expression": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [instructions]", + "expression-txn": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [TXN]" + }, + { + "name": "metric_LLC data read MPI (demand+prefetch)", + "name-txn": "metric_LLC data read (demand+prefetch) misses per txn", + "expression": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [instructions]", + "expression-txn": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [TXN]" + }, + { + "name": "metric_LLC total HITM (per instr) (excludes LLC prefetches)", + "name-txn": "metric_LLC total HITM per txn (excludes LLC prefetches)", + "expression": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM] / [instructions]", + "expression-txn": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_LLC total HIT clean line forwards (per instr) (excludes LLC prefetches)", + "name-txn": "metric_LLC total HIT clean line forwards per txn (excludes LLC prefetches)", + "expression": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD] / [instructions]", + "expression-txn": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_Average LLC demand data read miss latency (in ns)", + "expression": "1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]))" + }, + { + "name": "metric_Average LLC demand data read miss latency for LOCAL requests (in ns)", + "expression": "1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]))" + }, + { + "name": "metric_Average LLC demand data read miss latency for REMOTE requests (in ns)", + "expression": "1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE]) / ([UNC_CHA_CLOCKTICKS]/([CHAS_PER_SOCKET] * [SOCKET_COUNT]))" + }, + { + "name": "metric_UPI Data transmit BW (MB/sec) (only data)", + "expression": "[UNC_UPI_TxL_FLITS.ALL_DATA] * (64/9.0) / 1000000" + }, + { + "name": "metric_package power (watts)", + "expression": "[power/energy-pkg/]" + }, + { + "name": "metric_DRAM power (watts)", + "expression": "[power/energy-ram/]" + }, + { + "name": "metric_core c6 residency %", + "expression": "100 * [cstate_core/c6-residency/] / [TSC]" + }, + { + "name": "metric_package c6 residency %", + "expression": "100 * [cstate_pkg/c6-residency/] * [CORES_PER_SOCKET] / [TSC]" + }, + { + "name": "metric_% Uops delivered from decoded Icache (DSB)", + "expression": "100 * ([IDQ.DSB_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]))" + }, + { + "name": "metric_% Uops delivered from legacy decode pipeline (MITE)", + "expression": "100 * ([IDQ.MITE_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]))" + }, + { + "name": "metric_core initiated local dram read bandwidth (MB/sec)", + "expression": "([OCR.READS_TO_CORE.LOCAL_DRAM] + [OCR.HWPF_L3.L3_MISS_LOCAL]) * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_core initiated remote dram read bandwidth (MB/sec)", + "expression": "([OCR.READS_TO_CORE.REMOTE_DRAM] + [OCR.HWPF_L3.REMOTE]) * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_memory bandwidth read (MB/sec)", + "expression": "([UNC_M_CAS_COUNT_SCH0.RD] + [UNC_M_CAS_COUNT_SCH1.RD]) * 64 / 1000000" + }, + { + "name": "metric_memory bandwidth write (MB/sec)", + "expression": "([UNC_M_CAS_COUNT_SCH0.WR] + [UNC_M_CAS_COUNT_SCH1.WR]) * 64 / 1000000" + }, + { + "name": "metric_memory bandwidth total (MB/sec)", + "expression": "([UNC_M_CAS_COUNT_SCH0.RD] + [UNC_M_CAS_COUNT_SCH1.RD] + [UNC_M_CAS_COUNT_SCH0.WR] + [UNC_M_CAS_COUNT_SCH1.WR]) * 64 / 1000000" + }, + { + "name": "metric_ITLB (2nd level) MPI", + "name-txn": "metric_ITLB (2nd level) misses per txn", + "expression": "[ITLB_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[ITLB_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) load MPI", + "name-txn": "metric_DTLB (2nd level) load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) 2MB large page load MPI", + "name-txn": "metric_DTLB (2nd level) 2MB large page load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) store MPI", + "name-txn": "metric_DTLB (2nd level) store misses per txn", + "expression": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_NUMA %_Reads addressed to local DRAM", + "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" + }, + { + "name": "metric_NUMA %_Reads addressed to remote DRAM", + "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" + }, + { + "name": "metric_uncore frequency GHz", + "expression": "([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) / 1000000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_writes (MB/sec)", + "expression": "([UNC_CHA_TOR_INSERTS.IO_PCIRDCUR] * 64 / 1000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_reads (MB/sec)", + "expression": "(([UNC_CHA_TOR_INSERTS.IO_ITOM] + [UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR]) * 64 / 1000000) / 1" + }, + { + "name": "metric_TMA_Frontend_Bound(%)", + "expression": "100 * ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) )" + }, + { + "name": "metric_TMA_..Fetch_Latency(%)", + "expression": "100 * ( ( [PERF_METRICS.FETCH_LATENCY] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) )" + }, + { + "name": "metric_TMA_....ICache_Misses(%)", + "expression": "100 * ( [ICACHE_DATA.STALLS] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....ITLB_Misses(%)", + "expression": "100 * ( [ICACHE_TAG.STALLS] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....Branch_Resteers(%)", + "expression": "100 * ( [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) + ( [INT_MISC.UNKNOWN_BRANCH_CYCLES] / ( [cpu-cycles] ) ) )" + }, + { + "name": "metric_TMA_......Mispredicts_Resteers(%)", + "expression": "100 * ( ( ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) / ( max( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) , 0 ) ) ) * [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Clears_Resteers(%)", + "expression": "100 * ( ( 1 - ( ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) / ( max( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) , 0 ) ) ) ) * [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Unknown_Branches(%)", + "expression": "100 * ( [INT_MISC.UNKNOWN_BRANCH_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_..Fetch_Bandwidth(%)", + "expression": "100 * ( max( ( 0 ) , ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) - ( ( [PERF_METRICS.FETCH_LATENCY] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) ) ) ) )" + }, + { + "name": "metric_TMA_Bad_Speculation(%)", + "expression": "100 * ( max( ( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) ) , ( 0 ) ) )" + }, + { + "name": "metric_TMA_..Branch_Mispredicts(%)", + "expression": "100 * ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_..Machine_Clears(%)", + "expression": "100 * ( max( ( 0 ) , ( ( max( ( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) ) , ( 0 ) ) ) - ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) ) )" + }, + { + "name": "metric_TMA_Backend_Bound(%)", + "expression": "100 * ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_..Memory_Bound(%)", + "expression": "100 * ( [PERF_METRICS.MEMORY_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_....L1_Bound(%)", + "expression": "100 * ( max( ( ( [EXE_ACTIVITY.BOUND_ON_LOADS] - [MEMORY_ACTIVITY.STALLS_L1D_MISS] ) / ( [cpu-cycles] ) ) , ( 0 ) ) )" + }, + { + "name": "metric_TMA_......DTLB_Load(%)", + "expression": "100 * ( min( ( ( 7 ) * [DTLB_LOAD_MISSES.STLB_HIT:c1] + [DTLB_LOAD_MISSES.WALK_ACTIVE] ) , ( max( ( [CYCLE_ACTIVITY.CYCLES_MEM_ANY] - [MEMORY_ACTIVITY.CYCLES_L1D_MISS] ) , ( 0 ) ) ) ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Lock_Latency(%)", + "expression": "100 * ( ( 16 * max( ( 0 ) , ( [MEM_INST_RETIRED.LOCK_LOADS] - [L2_RQSTS.ALL_RFO] ) ) + ( [MEM_INST_RETIRED.LOCK_LOADS] / [MEM_INST_RETIRED.ALL_STORES] ) * ( ( 10 ) * [L2_RQSTS.RFO_HIT] + ( min( ( [cpu-cycles] - 0 ) , ( [OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO] - 0 ) ) ) ) ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....L2_Bound(%)", + "expression": "100 * ( ( [MEMORY_ACTIVITY.STALLS_L1D_MISS] - [MEMORY_ACTIVITY.STALLS_L2_MISS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....L3_Bound(%)", + "expression": "100 * ( ( [MEMORY_ACTIVITY.STALLS_L2_MISS] - [MEMORY_ACTIVITY.STALLS_L3_MISS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Data_Sharing(%)", + "expression": "100 * ( ( ( 79 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( ( 1000 / 1000 ) ) ) ) - ( 4.4 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( ( 1000 / 1000 ) ) ) ) ) * ( [MEM_LOAD_L3_HIT_RETIRED.XSNP_NO_FWD] + [MEM_LOAD_L3_HIT_RETIRED.XSNP_FWD] * ( 1 - ( [OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HITM] / ( [OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HITM] + [OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HIT_WITH_FWD] ) ) ) ) * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) / 2 ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....DRAM_Bound(%)", + "expression": "100 * ( ( ( [MEMORY_ACTIVITY.STALLS_L3_MISS] / ( [cpu-cycles] ) ) - ( ( ( ( ( 1 - ( ( 19 * ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + 10 * ( ( [MEM_LOAD_L3_MISS_RETIRED.LOCAL_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_FWD] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) ) ) / ( ( 19 * ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + 10 * ( ( [MEM_LOAD_L3_MISS_RETIRED.LOCAL_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_FWD] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) ) ) + ( 25 * ( 1 * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + 33 * ( 1 * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) ) ) ) ) * ( [MEMORY_ACTIVITY.STALLS_L3_MISS] / ( [cpu-cycles] ) ) ) ) if ( ( ( 1000000 ) * 1 > [MEM_LOAD_RETIRED.L1_MISS] ) ) else 0 ) ) ) )" + }, + { + "name": "metric_TMA_......MEM_Bandwidth(%)", + "expression": "100 * ( ( min( ( [cpu-cycles] - 0 ) , ( [OFFCORE_REQUESTS_OUTSTANDING.DATA_RD:c4] - 0 ) ) ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......MEM_Latency(%)", + "expression": "100 * ( ( min( ( [cpu-cycles] - 0 ) , ( [OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DATA_RD] - 0 ) ) ) / ( [cpu-cycles] ) - ( ( min( ( [cpu-cycles] - 0 ) , ( [OFFCORE_REQUESTS_OUTSTANDING.DATA_RD:c4] - 0 ) ) ) / ( [cpu-cycles] ) ) )" + }, + { + "name": "metric_TMA_....Store_Bound(%)", + "expression": "100 * ( [EXE_ACTIVITY.BOUND_ON_STORES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......False_Sharing(%)", + "expression": "100 * ( ( 81 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( ( 1000 / 1000 ) ) ) ) * [OCR.DEMAND_RFO.L3_HIT.SNOOP_HITM] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_..Core_Bound(%)", + "expression": "100 * ( max( ( 0 ) , ( ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) - ( [PERF_METRICS.MEMORY_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) ) )" + }, + { + "name": "metric_TMA_........AMX_Busy(%)", + "expression": "100 * ( [EXE.AMX_BUSY] / ( [CPU_CLK_UNHALTED.DISTRIBUTED] ) )", + "origin": "perfspect" + }, + { + "name": "metric_TMA_......Ports_Utilized_1(%)", + "expression": "100 * ( [EXE_ACTIVITY.1_PORTS_UTIL] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Ports_Utilized_2(%)", + "expression": "100 * ( [EXE_ACTIVITY.2_PORTS_UTIL] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Ports_Utilized_3m(%)", + "expression": "100 * ( [UOPS_EXECUTED.CYCLES_GE_3] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_Retiring(%)", + "expression": "100 * ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_..Light_Operations(%)", + "expression": "100 * ( max( ( 0 ) , ( ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) - ( [PERF_METRICS.HEAVY_OPERATIONS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) ) )" + }, + { + "name": "metric_TMA_........FP_Vector_256b(%)", + "expression": "100 * ( ( [FP_ARITH_INST_RETIRED.256B_PACKED_DOUBLE] + [FP_ARITH_INST_RETIRED.256B_PACKED_SINGLE] + [FP_ARITH_INST_RETIRED2.256B_PACKED_HALF] ) / ( ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * ( [TOPDOWN.SLOTS] ) ) )" + }, + { + "name": "metric_TMA_........FP_Vector_512b(%)", + "expression": "100 * ( ( [FP_ARITH_INST_RETIRED.512B_PACKED_DOUBLE] + [FP_ARITH_INST_RETIRED.512B_PACKED_SINGLE] + [FP_ARITH_INST_RETIRED2.512B_PACKED_HALF] ) / ( ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * ( [TOPDOWN.SLOTS] ) ) )" + }, + { + "name": "metric_TMA_......Int_Vector_256b(%)", + "expression": "100 * ( ( [INT_VEC_RETIRED.ADD_256] + [INT_VEC_RETIRED.MUL_256] + [INT_VEC_RETIRED.VNNI_256] ) / ( ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * ( [TOPDOWN.SLOTS] ) ) )" + }, + { + "name": "metric_TMA_..Heavy_Operations(%)", + "expression": "100 * ( [PERF_METRICS.HEAVY_OPERATIONS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_....Microcode_Sequencer(%)", + "expression": "100 * ( [UOPS_RETIRED.MS] / ( [TOPDOWN.SLOTS] ) )" + }, + { + "name": "metric_TMA_Info_Thread_IPC", + "expression": "[instructions] / ( [cpu-cycles] )" + } +] \ No newline at end of file diff --git a/events/metric_icx.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/icx.json similarity index 100% rename from events/metric_icx.json rename to cmd/metrics/resources/metrics/x86_64/GenuineIntel/icx.json diff --git a/events/metric_icx_nofixedtma.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/icx_nofixedtma.json similarity index 100% rename from events/metric_icx_nofixedtma.json rename to cmd/metrics/resources/metrics/x86_64/GenuineIntel/icx_nofixedtma.json diff --git a/cmd/metrics/resources/metrics/x86_64/GenuineIntel/skx.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/skx.json new file mode 100644 index 0000000..f167036 --- /dev/null +++ b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/skx.json @@ -0,0 +1,466 @@ +[ + { + "name": "metric_CPU operating frequency (in GHz)", + "expression": "(([cpu-cycles] / [ref-cycles] * [SYSTEM_TSC_FREQ]) / 1000000000)" + }, + { + "name": "metric_CPU utilization %", + "expression": "100 * [ref-cycles] / [TSC]" + }, + { + "name": "metric_CPU utilization% in kernel mode", + "expression": "100 * [ref-cycles:k] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_CPI", + "name-txn": "metric_cycles per txn", + "expression": "[cpu-cycles] / [instructions]", + "expression-txn": "[cpu-cycles] / [TXN]" + }, + { + "name": "metric_kernel_CPI", + "name-txn": "metric_kernel_cycles per txn", + "expression": "[cpu-cycles:k] / [instructions:k]", + "expression-txn": "[cpu-cycles:k] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_IPC", + "name-txn": "metric_txn per cycle", + "expression": "[instructions] / [cpu-cycles]", + "expression-txn": "[TXN] / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_locks retired per instr", + "name-txn": "metric_locks retired per txn", + "expression": "[MEM_INST_RETIRED.LOCK_LOADS] / [instructions]", + "expression-txn": "[MEM_INST_RETIRED.LOCK_LOADS] / [TXN]", + "origin": "perfmon website" + }, + { + "name": "metric_L1D MPI (includes data+rfo w/ prefetches)", + "name-txn": "metric_L1D misses per txn (includes data+rfo w/ prefetches)", + "expression": "[L1D.REPLACEMENT] / [instructions]", + "expression-txn": "[L1D.REPLACEMENT] / [TXN]" + }, + { + "name": "metric_L1D demand data read hits per instr", + "name-txn": "metric_L1D demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L1_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L1_HIT] / [TXN]" + }, + { + "name": "metric_L1-I code read misses (w/ prefetches) per instr", + "name-txn": "metric_L1I code read misses (includes prefetches) per txn", + "expression": "[L2_RQSTS.ALL_CODE_RD] / [instructions]", + "expression-txn": "[L2_RQSTS.ALL_CODE_RD] / [TXN]" + }, + { + "name": "metric_L2 demand data read hits per instr", + "name-txn": "metric_L2 demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L2_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L2_HIT] / [TXN]" + }, + { + "name": "metric_L2 MPI (includes code+data+rfo w/ prefetches)", + "name-txn": "metric_L2 misses per txn (includes code+data+rfo w/ prefetches)", + "expression": "[L2_LINES_IN.ALL] / [instructions]", + "expression-txn": "[L2_LINES_IN.ALL] / [TXN]" + }, + { + "name": "metric_L2 demand data read MPI", + "name-txn": "metric_L2 demand data read misses per txn", + "expression": "[MEM_LOAD_RETIRED.L2_MISS] / [instructions]", + "exression-txn": "[MEM_LOAD_RETIRED.L2_MISS] / [TXN]" + }, + { + "name": "metric_L2 demand code MPI", + "name-txn": "metric_L2 demand code misses per txn", + "expression": "[L2_RQSTS.CODE_RD_MISS] / [instructions]", + "expression-txn": "[L2_RQSTS.CODE_RD_MISS] / [TXN]" + }, + { + "name": "metric_LLC MPI (includes code+data+rfo w/ prefetches)", + "name-txn": "metric_LLC misses per txn (includes code+data+rfo w/ prefetches)", + "expression": "([UNC_CHA_TOR_INSERTS.IA_MISS.0x12CC0233] + [UNC_CHA_TOR_INSERTS.IA_MISS.0x12D40433] + [UNC_CHA_TOR_INSERTS.IA_MISS.0x12C40033]) / [instructions]", + "expression-txn": "([UNC_CHA_TOR_INSERTS.IA_MISS.0x12CC0233] + [UNC_CHA_TOR_INSERTS.IA_MISS.0x12D40433] + [UNC_CHA_TOR_INSERTS.IA_MISS.0x12C40033]) / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_LLC code read MPI (demand+prefetch)", + "name-txn": "metric_LLC code read (demand+prefetch) misses per txn", + "expression": "[UNC_CHA_TOR_INSERTS.IA_MISS.0x12CC0233] / [instructions]", + "expression-txn": "[UNC_CHA_TOR_INSERTS.IA_MISS.0x12CC0233] / [TXN]" + }, + { + "name": "metric_LLC data read MPI (demand+prefetch)", + "name-txn": "metric_LLC data read (demand+prefetch) misses per txn", + "expression": "[UNC_CHA_TOR_INSERTS.IA_MISS.0x12D40433] / [instructions]", + "expression-txn": "[UNC_CHA_TOR_INSERTS.IA_MISS.0x12D40433] / [TXN]" + }, + { + "name": "metric_LLC total HITM (per instr)", + "name-txn": "metric_LLC total HITM per txn (excludes LLC prefetches)", + "expression": "[OCR.ALL_READS.L3_MISS.REMOTE_HITM] / [instructions]", + "expression-txn": "[OCR.ALL_READS.L3_MISS.REMOTE_HITM] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_LLC total HIT clean line forwards (per instr)", + "name-txn": "metric_LLC total HIT clean line forwards per txn (excludes LLC prefetches)", + "expression": "[OCR.ALL_READS.L3_MISS.REMOTE_HIT_FORWARD] / [instructions]", + "expression-txn": "[OCR.ALL_READS.L3_MISS.REMOTE_HIT_FORWARD] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_Average LLC data read miss latency (in ns)", + "expression": "(1000000000 * [UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40433] / [UNC_CHA_TOR_INSERTS.IA_MISS.0x40433]) / ( [UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) )", + "origin": "perfspect" + }, + { + "name": "metric_Average LLC data read miss latency for LOCAL requests (in ns)", + "expression": "(1000000000 * [UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40432] / [UNC_CHA_TOR_INSERTS.IA_MISS.0x40432]) / ( [UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) )", + "origin": "perfspect" + }, + { + "name": "metric_Average LLC data read miss latency for REMOTE requests (in ns)", + "expression": "(1000000000 * [UNC_CHA_TOR_OCCUPANCY.IA_MISS.0x40431] / [UNC_CHA_TOR_INSERTS.IA_MISS.0x40431]) / ( [UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) )", + "origin": "perfspect" + }, + { + "name": "metric_ITLB MPI", + "name-txn": "metric_ITLB misses per txn", + "expression": "[ITLB_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[ITLB_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_ITLB large page MPI", + "name-txn": "metric_ITLB large page misses per txn", + "expression": "[ITLB_MISSES.WALK_COMPLETED_2M_4M] / [instructions]", + "expression-txn": "[ITLB_MISSES.WALK_COMPLETED_2M_4M] / [TXN]" + }, + { + "name": "metric_DTLB load MPI", + "name-txn": "metric_DTLB load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB 4KB page load MPI", + "name-txn": "metric_DTLB 4KB page load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED_4K] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED_4K] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_DTLB 2MB large page load MPI", + "name-txn": "metric_DTLB 2MB large page load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [TXN]" + }, + { + "name": "metric_DTLB 1GB large page load MPI", + "name-txn": "metric_DTLB 1GB large page load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED_1G] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED_1G] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_DTLB store MPI", + "name-txn": "metric_DTLB store misses per txn", + "expression": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB load miss latency (in core clks)", + "expression": "[DTLB_LOAD_MISSES.WALK_ACTIVE] / [DTLB_LOAD_MISSES.WALK_COMPLETED]", + "origin": "perfspect" + }, + { + "name": "metric_DTLB store miss latency (in core clks)", + "expression": "[DTLB_STORE_MISSES.WALK_ACTIVE] / [DTLB_STORE_MISSES.WALK_COMPLETED]", + "origin": "perfspect" + }, + { + "name": "metric_ITLB miss latency (in core clks)", + "expression": "[ITLB_MISSES.WALK_ACTIVE] / [ITLB_MISSES.WALK_COMPLETED]", + "origin": "perfspect" + }, + { + "name": "metric_NUMA %_Reads addressed to local DRAM", + "expression": "100 * [UNC_CHA_TOR_INSERTS.IA_MISS.0x40432] / ([UNC_CHA_TOR_INSERTS.IA_MISS.0x40432] + [UNC_CHA_TOR_INSERTS.IA_MISS.0x40431])" + }, + { + "name": "metric_NUMA %_Reads addressed to remote DRAM", + "expression": "100 * [UNC_CHA_TOR_INSERTS.IA_MISS.0x40431] / ([UNC_CHA_TOR_INSERTS.IA_MISS.0x40432] + [UNC_CHA_TOR_INSERTS.IA_MISS.0x40431])" + }, + { + "name": "metric_UPI Data transmit BW (MB/sec) (only data)", + "expression": "([UNC_UPI_TxL_FLITS.ALL_DATA] * (64 / 9.0) / 1000000) / 1" + }, + { + "name": "metric_UPI Transmit utilization_% (includes control)", + "expression": "100 * (([UNC_UPI_TxL_FLITS.ALL_DATA] + [UNC_UPI_TxL_FLITS.NON_DATA]) / 3) / ((((([SYSTEM_TSC_FREQ] / ([CHAS_PER_SOCKET] * [CONST_THREAD_COUNT])) / (([SYSTEM_TSC_FREQ] / ([CHAS_PER_SOCKET] * [CONST_THREAD_COUNT])) - [cstate_pkg/c6-residency/])) * ([UNC_UPI_CLOCKTICKS] - [UNC_UPI_L1_POWER_CYCLES])) * 5 / 6))", + "origin": "perfspect" + }, + { + "name": "metric_uncore frequency GHz", + "expression": "([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) / 1000000000) / 1" + }, + { + "name": "metric_package power (watts)", + "expression": "[power/energy-pkg/]", + "origin": "perfspect" + }, + { + "name": "metric_DRAM power (watts)", + "expression": "[power/energy-ram/]", + "origin": "perfspect" + }, + { + "name": "metric_core c6 residency %", + "expression": "100 * [cstate_core/c6-residency/] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_package c6 residency %", + "expression": "100 * [cstate_pkg/c6-residency/] * [CORES_PER_SOCKET] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_% Uops delivered from decoded Icache (DSB)", + "expression": "100 * ([IDQ.DSB_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" + }, + { + "name": "metric_% Uops delivered from legacy decode pipeline (MITE)", + "expression": "100 * ([IDQ.MITE_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" + }, + { + "name": "metric_core % cycles in non AVX license", + "expression": "(100 * [CORE_POWER.LVL0_TURBO_LICENSE]) / ([CORE_POWER.LVL0_TURBO_LICENSE] + [CORE_POWER.LVL1_TURBO_LICENSE] + [CORE_POWER.LVL2_TURBO_LICENSE])", + "origin": "perfspect" + }, + { + "name": "metric_core % cycles in AVX2 license", + "expression": "(100 * [CORE_POWER.LVL1_TURBO_LICENSE]) / ([CORE_POWER.LVL0_TURBO_LICENSE] + [CORE_POWER.LVL1_TURBO_LICENSE] + [CORE_POWER.LVL2_TURBO_LICENSE])", + "origin": "perfspect" + }, + { + "name": "metric_core % cycles in AVX-512 license", + "expression": "(100 * [CORE_POWER.LVL2_TURBO_LICENSE]) / ([CORE_POWER.LVL0_TURBO_LICENSE] + [CORE_POWER.LVL1_TURBO_LICENSE] + [CORE_POWER.LVL2_TURBO_LICENSE])", + "origin": "perfspect" + }, + { + "name": "metric_core initiated local dram read bandwidth (MB/sec)", + "expression": "[OCR.ALL_READS.L3_MISS_LOCAL_DRAM.ANY_SNOOP] * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_core initiated remote dram read bandwidth (MB/sec)", + "expression": "[OCR.ALL_READS.L3_MISS_LOCAL_DRAM.ANY_SNOOP_ocr_msr_3fB80007f7] * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_memory bandwidth read (MB/sec)", + "expression": "([UNC_M_CAS_COUNT.RD] * 64 / 1000000) / 1" + }, + { + "name": "metric_memory bandwidth write (MB/sec)", + "expression": "([UNC_M_CAS_COUNT.WR] * 64 / 1000000) / 1" + }, + { + "name": "metric_memory bandwidth total (MB/sec)", + "expression": "(([UNC_M_CAS_COUNT.RD] + [UNC_M_CAS_COUNT.WR]) * 64 / 1000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_writes (MB/sec)", + "expression": "(([UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART0] + [UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART1] + [UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART2] + [UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART3]) * 4 / 1000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_reads (MB/sec)", + "expression": "(([UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART0] + [UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART1] + [UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART2] + [UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART3]) * 4 / 1000000) / 1" + }, + { + "name": "metric_TMA_Info_cycles_both_threads_active(%)", + "expression": "100 * ( (1 - ([CPU_CLK_THREAD_UNHALTED.ONE_THREAD_ACTIVE] / ([CPU_CLK_THREAD_UNHALTED.REF_XCLK_ANY] / 2)) ) if [CONST_THREAD_COUNT] > 1 else 0)", + "origin": "perfspect" + }, + { + "name": "metric_TMA_Info_CoreIPC", + "expression": "[instructions] / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", + "origin": "perfspect" + }, + { + "name": "metric_TMA_Frontend_Bound(%)", + "expression": "100 * ( [IDQ_UOPS_NOT_DELIVERED.CORE] / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) )" + }, + { + "name": "metric_TMA_..Frontend_Latency(%)", + "expression": "100 * [IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE] / ([CPU_CLK_UNHALTED.THREAD_ANY] /[CONST_THREAD_COUNT])", + "origin": "perfspect" + }, + { + "name": "metric_TMA_....ICache_Misses(%)", + "expression": "100 * ( ( [ICACHE_16B.IFDATA_STALL] + 2 * [ICACHE_16B.IFDATA_STALL:c1:e1] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....ITLB_Misses(%)", + "expression": "100 * ( [ICACHE_64B.IFTAG_STALL] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....Branch_Resteers(%)", + "expression": "100 * ( [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) + ( ( 9 ) * [BACLEARS.ANY] / ( [cpu-cycles] ) ) )" + }, + { + "name": "metric_TMA_......Mispredicts_Resteers(%)", + "expression": "100 * ( ( [BR_MISP_RETIRED.ALL_BRANCHES] / ( [BR_MISP_RETIRED.ALL_BRANCHES] + [MACHINE_CLEARS.COUNT] ) ) * [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Clears_Resteers(%)", + "expression": "100 * ( ( 1 - ( [BR_MISP_RETIRED.ALL_BRANCHES] / ( [BR_MISP_RETIRED.ALL_BRANCHES] + [MACHINE_CLEARS.COUNT] ) ) ) * [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Unknown_Branches_Resteers(%)", + "expression": "100 * (9 * [BACLEARS.ANY]) / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_TMA_..Frontend_Bandwidth(%)", + "expression": "100 * ([IDQ_UOPS_NOT_DELIVERED.CORE] - 4 * [IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE]) / (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT]))", + "origin": "perfspect" + }, + { + "name": "metric_TMA_....MITE(%)", + "expression": "100 * ( ( [IDQ.ALL_MITE_CYCLES_ANY_UOPS] - [IDQ.ALL_MITE_CYCLES_4_UOPS] ) / ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) / 2 )" + }, + { + "name": "metric_TMA_....DSB(%)", + "expression": "100 * ( ( [IDQ.ALL_DSB_CYCLES_ANY_UOPS] - [IDQ.ALL_DSB_CYCLES_4_UOPS] ) / ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) / 2 )" + }, + { + "name": "metric_TMA_Bad_Speculation(%)", + "expression": "100 * ( ( [UOPS_ISSUED.ANY] - ( [UOPS_RETIRED.RETIRE_SLOTS] ) + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) )" + }, + { + "name": "metric_TMA_..Branch_Mispredicts(%)", + "expression": "100 * ( ( [BR_MISP_RETIRED.ALL_BRANCHES] / ( [BR_MISP_RETIRED.ALL_BRANCHES] + [MACHINE_CLEARS.COUNT] ) ) * ( ( [UOPS_ISSUED.ANY] - ( [UOPS_RETIRED.RETIRE_SLOTS] ) + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) )" + }, + { + "name": "metric_TMA_..Machine_Clears(%)", + "expression": "100 * ( ( ( [UOPS_ISSUED.ANY] - ( [UOPS_RETIRED.RETIRE_SLOTS] ) + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) - ( ( [BR_MISP_RETIRED.ALL_BRANCHES] / ( [BR_MISP_RETIRED.ALL_BRANCHES] + [MACHINE_CLEARS.COUNT] ) ) * ( ( [UOPS_ISSUED.ANY] - ( [UOPS_RETIRED.RETIRE_SLOTS] ) + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) ) )" + }, + { + "name": "metric_TMA_Backend_Bound(%)", + "expression": "100 * ( 1 - ( [IDQ_UOPS_NOT_DELIVERED.CORE] / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) - ( [UOPS_ISSUED.ANY] + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) )" + }, + { + "name": "metric_TMA_..Memory_Bound(%)", + "expression": "100 * ( ( ( [CYCLE_ACTIVITY.STALLS_MEM_ANY] + [EXE_ACTIVITY.BOUND_ON_STORES] ) / ( [CYCLE_ACTIVITY.STALLS_TOTAL] + ( [EXE_ACTIVITY.1_PORTS_UTIL] + ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) * [EXE_ACTIVITY.2_PORTS_UTIL] ) + [EXE_ACTIVITY.BOUND_ON_STORES] ) ) * ( 1 - ( [IDQ_UOPS_NOT_DELIVERED.CORE] / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) - ( [UOPS_ISSUED.ANY] + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) )" + }, + { + "name": "metric_TMA_....L1_Bound(%)", + "expression": "100 * ( max( ( [CYCLE_ACTIVITY.STALLS_MEM_ANY] - [CYCLE_ACTIVITY.STALLS_L1D_MISS] ) / ( [cpu-cycles] ) , 0 ) )" + }, + { + "name": "metric_TMA_......DTLB_Load(%)", + "expression": "100 * ( min( ( 9 ) * [DTLB_LOAD_MISSES.STLB_HIT:c1] + [DTLB_LOAD_MISSES.WALK_ACTIVE] , max( [CYCLE_ACTIVITY.CYCLES_MEM_ANY] - [CYCLE_ACTIVITY.CYCLES_L1D_MISS] , 0 ) ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Lock_Latency(%)", + "expression": "100 * ( min( ( ( 12 * max( 0 , [MEM_INST_RETIRED.LOCK_LOADS] - [L2_RQSTS.ALL_RFO] ) + ( [MEM_INST_RETIRED.LOCK_LOADS] / [MEM_INST_RETIRED.ALL_STORES] ) * ( ( 11 ) * [L2_RQSTS.RFO_HIT] + ( min( [cpu-cycles] , [OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO] ) ) ) ) / ( [cpu-cycles] ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_....L2_Bound(%)", + "expression": "100 * ( ( ( [MEM_LOAD_RETIRED.L2_HIT] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) / ( ( [MEM_LOAD_RETIRED.L2_HIT] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + [L1D_PEND_MISS.FB_FULL:c1] ) ) * ( ( [CYCLE_ACTIVITY.STALLS_L1D_MISS] - [CYCLE_ACTIVITY.STALLS_L2_MISS] ) / ( [cpu-cycles] ) ) )" + }, + { + "name": "metric_TMA_....L3_Bound(%)", + "expression": "100 * ( ( [CYCLE_ACTIVITY.STALLS_L2_MISS] - [CYCLE_ACTIVITY.STALLS_L3_MISS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Data_Sharing(%)", + "expression": "100 * ( min( ( ( ( 47.5 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( 1000 / 1000 ) ) ) - ( 3.5 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( 1000 / 1000 ) ) ) ) * ( [MEM_LOAD_L3_HIT_RETIRED.XSNP_HIT] + [MEM_LOAD_L3_HIT_RETIRED.XSNP_HITM] * ( 1 - ( [OCR.DEMAND_DATA_RD.L3_HIT.HITM_OTHER_CORE] / ( [OCR.DEMAND_DATA_RD.L3_HIT.HITM_OTHER_CORE] + [OCR.DEMAND_DATA_RD.L3_HIT.HIT_OTHER_CORE_FWD] ) ) ) ) * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) / 2 ) / ( [cpu-cycles] ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_....MEM_Bound(%)", + "expression": "100 * [CYCLE_ACTIVITY.STALLS_L3_MISS] / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_TMA_......MEM_Bandwidth(%)", + "expression": "100 * min([OFFCORE_REQUESTS_OUTSTANDING.L3_MISS_DEMAND_DATA_RD_GE_6] , [cpu-cycles]) / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_TMA_......MEM_Latency(%)", + "expression": "100 * (min([OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_L3_MISS_DEMAND_DATA_RD] , [cpu-cycles]) - min([OFFCORE_REQUESTS_OUTSTANDING.L3_MISS_DEMAND_DATA_RD_GE_6] , [cpu-cycles]))/ [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_TMA_....Store_Bound(%)", + "expression": "100 * ( [EXE_ACTIVITY.BOUND_ON_STORES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......False_Sharing(%)", + "expression": "100 * ( min( ( ( ( 110 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( 1000 / 1000 ) ) ) * ( [OCR.DEMAND_RFO.L3_MISS.REMOTE_HITM] + [OCR.PF_L2_RFO.L3_MISS.REMOTE_HITM] ) + ( 47.5 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( 1000 / 1000 ) ) ) * ( [OCR.DEMAND_RFO.L3_HIT.HITM_OTHER_CORE] + [OCR.PF_L2_RFO.L3_HIT.HITM_OTHER_CORE] ) ) / ( [cpu-cycles] ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_..Core_Bound(%)", + "expression": "100 * ( ( 1 - ( [IDQ_UOPS_NOT_DELIVERED.CORE] / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) - ( [UOPS_ISSUED.ANY] + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) - ( ( ( [CYCLE_ACTIVITY.STALLS_MEM_ANY] + [EXE_ACTIVITY.BOUND_ON_STORES] ) / ( [CYCLE_ACTIVITY.STALLS_TOTAL] + ( [EXE_ACTIVITY.1_PORTS_UTIL] + ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) * [EXE_ACTIVITY.2_PORTS_UTIL] ) + [EXE_ACTIVITY.BOUND_ON_STORES] ) ) * ( 1 - ( [IDQ_UOPS_NOT_DELIVERED.CORE] / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) - ( [UOPS_ISSUED.ANY] + ( 4 ) * ( ( [INT_MISC.RECOVERY_CYCLES_ANY] / 2 ) if [HYPERTHREADING_ON] else [INT_MISC.RECOVERY_CYCLES] ) ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) ) )" + }, + { + "name": "metric_TMA_....Ports_Utilization(%)", + "expression": "100 * ( ( [EXE_ACTIVITY.EXE_BOUND_0_PORTS] + ( [EXE_ACTIVITY.1_PORTS_UTIL] + ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) * [EXE_ACTIVITY.2_PORTS_UTIL] ) ) / ( [cpu-cycles] ) if ( [ARITH.DIVIDER_ACTIVE] < ( [CYCLE_ACTIVITY.STALLS_TOTAL] - [CYCLE_ACTIVITY.STALLS_MEM_ANY] ) ) else ( [EXE_ACTIVITY.1_PORTS_UTIL] + ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) * [EXE_ACTIVITY.2_PORTS_UTIL] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Ports_Utilized_0(%)", + "expression": "100 * (([UOPS_EXECUTED.CORE_CYCLES_NONE] / 2) if ([CONST_THREAD_COUNT] > 1) else [EXE_ACTIVITY.EXE_BOUND_0_PORTS]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", + "origin": "perfspect" + }, + { + "name": "metric_TMA_......Ports_Utilized_1(%)", + "expression": "100 * ((([UOPS_EXECUTED.CORE_CYCLES_GE_1] - [UOPS_EXECUTED.CORE_CYCLES_GE_2]) / 2) if ([CONST_THREAD_COUNT] > 1) else [EXE_ACTIVITY.1_PORTS_UTIL]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", + "origin": "perfspect" + }, + { + "name": "metric_TMA_......Ports_Utilized_2(%)", + "expression": "100 * ((([UOPS_EXECUTED.CORE_CYCLES_GE_2] - [UOPS_EXECUTED.CORE_CYCLES_GE_3]) / 2) if ([CONST_THREAD_COUNT] > 1) else [EXE_ACTIVITY.2_PORTS_UTIL]) / ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])", + "origin": "perfspect" + }, + { + "name": "metric_TMA_......Ports_Utilized_3m(%)", + "expression": "100 * [UOPS_EXECUTED.CORE_CYCLES_GE_3] / [CPU_CLK_UNHALTED.THREAD_ANY]", + "origin": "perfspect" + }, + { + "name": "metric_TMA_Retiring(%)", + "expression": "100 * ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) )" + }, + { + "name": "metric_TMA_..Light_Operations(%)", + "expression": "100 * ( ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) - ( ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) + [UOPS_RETIRED.MACRO_FUSED] - [instructions] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) )" + }, + { + "name": "metric_TMA_....FP_Arith(%)", + "expression": "100 * ( ( ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) ) * [UOPS_EXECUTED.X87] / [UOPS_EXECUTED.THREAD] ) + ( ( [FP_ARITH_INST_RETIRED.SCALAR_SINGLE:u0x03] ) / ( [UOPS_RETIRED.RETIRE_SLOTS] ) ) + ( min( ( ( [FP_ARITH_INST_RETIRED.128B_PACKED_DOUBLE:u0xfc] ) / ( [UOPS_RETIRED.RETIRE_SLOTS] ) ) , ( 1 ) ) ) )" + }, + { + "name": "metric_TMA_......FP_Scalar(%)", + "expression": "100 * ( ( [FP_ARITH_INST_RETIRED.SCALAR_SINGLE:u0x03] ) / ( [UOPS_RETIRED.RETIRE_SLOTS] ) )" + }, + { + "name": "metric_TMA_......FP_Vector(%)", + "expression": "100 * ( min( ( ( [FP_ARITH_INST_RETIRED.128B_PACKED_DOUBLE:u0xfc] ) / ( [UOPS_RETIRED.RETIRE_SLOTS] ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_..Heavy_Operations(%)", + "expression": "100 * ( ( ( [UOPS_RETIRED.RETIRE_SLOTS] ) + [UOPS_RETIRED.MACRO_FUSED] - [instructions] ) / ( ( 4 ) * ( ( [CPU_CLK_UNHALTED.THREAD_ANY] / 2 ) if [HYPERTHREADING_ON] else ( [cpu-cycles] ) ) ) )" + }, + { + "name": "metric_TMA_..Microcode_Sequencer(%)", + "expression": "100 * (([UOPS_RETIRED.RETIRE_SLOTS] / [UOPS_ISSUED.ANY]) * [IDQ.MS_UOPS] / (4 * ([CPU_CLK_UNHALTED.THREAD_ANY] / [CONST_THREAD_COUNT])))", + "origin": "perfspect" + } +] \ No newline at end of file diff --git a/cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr.json new file mode 100644 index 0000000..e81e6ed --- /dev/null +++ b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr.json @@ -0,0 +1,399 @@ +[ + { + "name": "metric_CPU operating frequency (in GHz)", + "expression": "(([cpu-cycles] / [ref-cycles] * [SYSTEM_TSC_FREQ]) / 1000000000)" + }, + { + "name": "metric_CPU utilization %", + "expression": "100 * [ref-cycles] / [TSC]" + }, + { + "name": "metric_CPU utilization% in kernel mode", + "expression": "100 * [ref-cycles:k] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_CPI", + "name-txn": "metric_cycles per txn", + "expression": "[cpu-cycles] / [instructions]", + "expression-txn": "[cpu-cycles] / [TXN]" + }, + { + "name": "metric_kernel_CPI", + "name-txn": "metric_kernel_cycles per txn", + "expression": "[cpu-cycles:k] / [instructions:k]", + "expression-txn": "[cpu-cycles:k] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_IPC", + "name-txn": "metric_txn per cycle", + "expression": "[instructions] / [cpu-cycles]", + "expression-txn": "[TXN] / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_giga_instructions_per_sec", + "expression": "[instructions] / 1000000000", + "origin": "perfspect" + }, + { + "name": "metric_locks retired per instr", + "name-txn": "metric_locks retired per txn", + "expression": "[MEM_INST_RETIRED.LOCK_LOADS] / [instructions]", + "expression-txn": "[MEM_INST_RETIRED.LOCK_LOADS] / [TXN]", + "origin": "perfmon website" + }, + { + "name": "metric_L1D MPI (includes data+rfo w/ prefetches)", + "name-txn": "metric_L1D misses per txn (includes data+rfo w/ prefetches)", + "expression": "[L1D.REPLACEMENT] / [instructions]", + "expression-txn": "[L1D.REPLACEMENT] / [TXN]" + }, + { + "name": "metric_L1D demand data read hits per instr", + "name-txn": "metric_L1D demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L1_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L1_HIT] / [TXN]" + }, + { + "name": "metric_L1-I code read misses (w/ prefetches) per instr", + "name-txn": "metric_L1I code read misses (includes prefetches) per txn", + "expression": "[L2_RQSTS.ALL_CODE_RD] / [instructions]", + "expression-txn": "[L2_RQSTS.ALL_CODE_RD] / [TXN]" + }, + { + "name": "metric_L2 demand data read hits per instr", + "name-txn": "metric_L2 demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L2_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L2_HIT] / [TXN]" + }, + { + "name": "metric_L2 MPI (includes code+data+rfo w/ prefetches)", + "name-txn": "metric_L2 misses per txn (includes code+data+rfo w/ prefetches)", + "expression": "[L2_LINES_IN.ALL] / [instructions]", + "expression-txn": "[L2_LINES_IN.ALL] / [TXN]" + }, + { + "name": "metric_L2 demand data read MPI", + "name-txn": "metric_L2 demand data read misses per txn", + "expression": "[MEM_LOAD_RETIRED.L2_MISS] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L2_MISS] / [TXN]" + }, + { + "name": "metric_L2 demand code MPI", + "name-txn": "metric_L2 demand code misses per txn", + "expression": "[L2_RQSTS.CODE_RD_MISS] / [instructions]", + "expression-txn": "[L2_RQSTS.CODE_RD_MISS] / [TXN]" + }, + { + "name": "metric_LLC code read MPI (demand+prefetch)", + "name-txn": "metric_LLC code read (demand+prefetch) misses per txn", + "expression": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [instructions]", + "expression-txn": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [TXN]" + }, + { + "name": "metric_LLC data read MPI (demand+prefetch)", + "name-txn": "metric_LLC data read (demand+prefetch) misses per txn", + "expression": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [instructions]", + "expression-txn": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [TXN]" + }, + { + "name": "metric_LLC total HITM (per instr) (excludes LLC prefetches)", + "name-txn": "metric_LLC total HITM per txn (excludes LLC prefetches)", + "expression": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM] / [instructions]", + "expression-txn": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_LLC total HIT clean line forwards (per instr) (excludes LLC prefetches)", + "name-txn": "metric_LLC total HIT clean line forwards per txn (excludes LLC prefetches)", + "expression": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD] / [instructions]", + "expression-txn": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_Average LLC demand data read miss latency (in ns)", + "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" + }, + { + "name": "metric_Average LLC demand data read miss latency for LOCAL requests (in ns)", + "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" + }, + { + "name": "metric_Average LLC demand data read miss latency for REMOTE requests (in ns)", + "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" + }, + { + "name": "metric_UPI Data transmit BW (MB/sec) (only data)", + "expression": "([UNC_UPI_TxL_FLITS.ALL_DATA] * (64 / 9.0) / 1000000) / 1" + }, + { + "name": "metric_package power (watts)", + "expression": "[power/energy-pkg/]", + "origin": "perfspect" + }, + { + "name": "metric_DRAM power (watts)", + "expression": "[power/energy-ram/]", + "origin": "perfspect" + }, + { + "name": "metric_core c6 residency %", + "expression": "100 * [cstate_core/c6-residency/] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_package c6 residency %", + "expression": "100 * [cstate_pkg/c6-residency/] * [CORES_PER_SOCKET] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_% Uops delivered from decoded Icache (DSB)", + "expression": "100 * ([IDQ.DSB_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" + }, + { + "name": "metric_% Uops delivered from legacy decode pipeline (MITE)", + "expression": "100 * ([IDQ.MITE_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" + }, + { + "name": "metric_core initiated local dram read bandwidth (MB/sec)", + "expression": "([OCR.READS_TO_CORE.LOCAL_DRAM] + [OCR.HWPF_L3.L3_MISS_LOCAL]) * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_core initiated remote dram read bandwidth (MB/sec)", + "expression": "([OCR.READS_TO_CORE.REMOTE_DRAM] + [OCR.HWPF_L3.REMOTE]) * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_memory bandwidth read (MB/sec)", + "expression": "([UNC_M_CAS_COUNT.RD] * 64 / 1000000) / 1" + }, + { + "name": "metric_memory bandwidth write (MB/sec)", + "expression": "([UNC_M_CAS_COUNT.WR] * 64 / 1000000) / 1" + }, + { + "name": "metric_memory bandwidth total (MB/sec)", + "expression": "(([UNC_M_CAS_COUNT.RD] + [UNC_M_CAS_COUNT.WR]) * 64 / 1000000) / 1" + }, + { + "name": "metric_ITLB (2nd level) MPI", + "name-txn": "metric_ITLB (2nd level) misses per txn", + "expression": "[ITLB_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[ITLB_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) load MPI", + "name-txn": "metric_DTLB (2nd level) load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) 2MB large page load MPI", + "name-txn": "metric_DTLB (2nd level) 2MB large page load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) store MPI", + "name-txn": "metric_DTLB (2nd level) store misses per txn", + "expression": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_NUMA %_Reads addressed to local DRAM", + "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" + }, + { + "name": "metric_NUMA %_Reads addressed to remote DRAM", + "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" + }, + { + "name": "metric_uncore frequency GHz", + "expression": "([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) / 1000000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_writes (MB/sec)", + "expression": "([UNC_CHA_TOR_INSERTS.IO_PCIRDCUR] * 64 / 1000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_reads (MB/sec)", + "expression": "(([UNC_CHA_TOR_INSERTS.IO_ITOM] + [UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR]) * 64 / 1000000) / 1" + }, + { + "name": "metric_TMA_Frontend_Bound(%)", + "expression": "100 * ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) )" + }, + { + "name": "metric_TMA_..Fetch_Latency(%)", + "expression": "100 * ( ( [PERF_METRICS.FETCH_LATENCY] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) )" + }, + { + "name": "metric_TMA_....ICache_Misses(%)", + "expression": "100 * ( [ICACHE_DATA.STALLS] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....ITLB_Misses(%)", + "expression": "100 * ( [ICACHE_TAG.STALLS] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....Branch_Resteers(%)", + "expression": "100 * ( [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) + ( [INT_MISC.UNKNOWN_BRANCH_CYCLES] / ( [cpu-cycles] ) ) )" + }, + { + "name": "metric_TMA_......Mispredicts_Resteers(%)", + "expression": "100 * ( ( ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) / ( max( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) , 0 ) ) ) * [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Clears_Resteers(%)", + "expression": "100 * ( ( 1 - ( ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) / ( max( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) , 0 ) ) ) ) * [INT_MISC.CLEAR_RESTEER_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Unknown_Branches(%)", + "expression": "100 * ( [INT_MISC.UNKNOWN_BRANCH_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_..Fetch_Bandwidth(%)", + "expression": "100 * ( max( 0 , ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) - ( ( [PERF_METRICS.FETCH_LATENCY] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) ) ) )" + }, + { + "name": "metric_TMA_....MITE(%)", + "expression": "100 * ( ( [IDQ.MITE_CYCLES_ANY] - [IDQ.MITE_CYCLES_OK] ) / ( [CPU_CLK_UNHALTED.DISTRIBUTED] ) / 2 )" + }, + { + "name": "metric_TMA_....DSB(%)", + "expression": "100 * ( ( [IDQ.DSB_CYCLES_ANY] - [IDQ.DSB_CYCLES_OK] ) / ( [CPU_CLK_UNHALTED.DISTRIBUTED] ) / 2 )" + }, + { + "name": "metric_TMA_Bad_Speculation(%)", + "expression": "100 * ( max( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) , 0 ) )" + }, + { + "name": "metric_TMA_..Branch_Mispredicts(%)", + "expression": "100 * ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_..Machine_Clears(%)", + "expression": "100 * ( max( 0 , ( max( 1 - ( ( [PERF_METRICS.FRONTEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) - [INT_MISC.UOP_DROPPING] / ( [TOPDOWN.SLOTS] ) ) + ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) , 0 ) ) - ( [PERF_METRICS.BRANCH_MISPREDICTS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) )" + }, + { + "name": "metric_TMA_Backend_Bound(%)", + "expression": "100 * ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_..Memory_Bound(%)", + "expression": "100 * ( [PERF_METRICS.MEMORY_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_....L1_Bound(%)", + "expression": "100 * ( max( ( [EXE_ACTIVITY.BOUND_ON_LOADS] - [MEMORY_ACTIVITY.STALLS_L1D_MISS] ) / ( [cpu-cycles] ) , 0 ) )" + }, + { + "name": "metric_TMA_......DTLB_Load(%)", + "expression": "100 * ( min( ( 7 ) * [DTLB_LOAD_MISSES.STLB_HIT:c1] + [DTLB_LOAD_MISSES.WALK_ACTIVE] , max( [CYCLE_ACTIVITY.CYCLES_MEM_ANY] - [MEMORY_ACTIVITY.CYCLES_L1D_MISS] , 0 ) ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Lock_Latency(%)", + "expression": "100 * ( min( ( ( 16 * max( 0 , [MEM_INST_RETIRED.LOCK_LOADS] - [L2_RQSTS.ALL_RFO] ) + ( [MEM_INST_RETIRED.LOCK_LOADS] / [MEM_INST_RETIRED.ALL_STORES] ) * ( ( 10 ) * [L2_RQSTS.RFO_HIT] + ( min( [cpu-cycles] , [OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO] ) ) ) ) / ( [cpu-cycles] ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_....L2_Bound(%)", + "expression": "100 * ( ( [MEMORY_ACTIVITY.STALLS_L1D_MISS] - [MEMORY_ACTIVITY.STALLS_L2_MISS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....L3_Bound(%)", + "expression": "100 * ( ( [MEMORY_ACTIVITY.STALLS_L2_MISS] - [MEMORY_ACTIVITY.STALLS_L3_MISS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Data_Sharing(%)", + "expression": "100 * ( min( ( ( ( 79.5 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( 1000 / 1000 ) ) ) - ( 4 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( 1000 / 1000 ) ) ) ) * ( [MEM_LOAD_L3_HIT_RETIRED.XSNP_NO_FWD] + [MEM_LOAD_L3_HIT_RETIRED.XSNP_FWD] * ( 1 - ( [OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HITM] / ( [OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HITM] + [OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HIT_WITH_FWD] ) ) ) ) * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) / 2 ) / ( [cpu-cycles] ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_....DRAM_Bound(%)", + "expression": "100 * ( min( ( ( ( [MEMORY_ACTIVITY.STALLS_L3_MISS] / ( [cpu-cycles] ) ) - ( min( ( ( ( ( 1 - ( ( ( 19 * ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + 10 * ( ( [MEM_LOAD_L3_MISS_RETIRED.LOCAL_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_FWD] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) ) ) / ( ( 19 * ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + 10 * ( ( [MEM_LOAD_L3_MISS_RETIRED.LOCAL_DRAM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_FWD] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) + ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) ) ) + ( 25 * ( ( [MEM_LOAD_RETIRED.LOCAL_PMM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) ) + 33 * ( ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_PMM] * ( 1 + ( [MEM_LOAD_RETIRED.FB_HIT] / [MEM_LOAD_RETIRED.L1_MISS] ) ) ) ) ) ) ) ) ) * ( [MEMORY_ACTIVITY.STALLS_L3_MISS] / ( [cpu-cycles] ) ) ) if ( ( 1000000 ) * ( [MEM_LOAD_L3_MISS_RETIRED.REMOTE_PMM] + [MEM_LOAD_RETIRED.LOCAL_PMM] ) > [MEM_LOAD_RETIRED.L1_MISS] ) else 0 ) ) , ( 1 ) ) ) ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_......MEM_Bandwidth(%)", + "expression": "100 * ( ( min( [cpu-cycles] , [OFFCORE_REQUESTS_OUTSTANDING.DATA_RD:c4] ) ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......MEM_Latency(%)", + "expression": "100 * ( ( min( [cpu-cycles] , [OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DATA_RD] ) ) / ( [cpu-cycles] ) - ( ( min( [cpu-cycles] , [OFFCORE_REQUESTS_OUTSTANDING.DATA_RD:c4] ) ) / ( [cpu-cycles] ) ) )" + }, + { + "name": "metric_TMA_....Store_Bound(%)", + "expression": "100 * ( [EXE_ACTIVITY.BOUND_ON_STORES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......False_Sharing(%)", + "expression": "100 * ( min( ( ( 80 * ( ( ( [cpu-cycles] ) / [ref-cycles] ) * [SYSTEM_TSC_FREQ] / ( 1000000000 ) / ( 1000 / 1000 ) ) ) * [OCR.DEMAND_RFO.L3_HIT.SNOOP_HITM] / ( [cpu-cycles] ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_..Core_Bound(%)", + "expression": "100 * ( max( 0 , ( [PERF_METRICS.BACKEND_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) - ( [PERF_METRICS.MEMORY_BOUND] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) )" + }, + { + "name": "metric_TMA_....Ports_Utilization(%)", + "expression": "100 * ( ( [EXE_ACTIVITY.3_PORTS_UTIL:u0x80] + ( [RESOURCE_STALLS.SCOREBOARD] / ( [cpu-cycles] ) ) * ( [CYCLE_ACTIVITY.STALLS_TOTAL] - [EXE_ACTIVITY.BOUND_ON_LOADS] ) + ( [EXE_ACTIVITY.1_PORTS_UTIL] + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * [EXE_ACTIVITY.2_PORTS_UTIL:u0xc] ) ) / ( [cpu-cycles] ) if ( [ARITH.DIV_ACTIVE] < ( [CYCLE_ACTIVITY.STALLS_TOTAL] - [EXE_ACTIVITY.BOUND_ON_LOADS] ) ) else ( [EXE_ACTIVITY.1_PORTS_UTIL] + ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * [EXE_ACTIVITY.2_PORTS_UTIL:u0xc] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Ports_Utilized_0(%)", + "expression": "100 * ( [EXE_ACTIVITY.3_PORTS_UTIL:u0x80] / ( [cpu-cycles] ) + ( [RESOURCE_STALLS.SCOREBOARD] / ( [cpu-cycles] ) ) * ( [CYCLE_ACTIVITY.STALLS_TOTAL] - [EXE_ACTIVITY.BOUND_ON_LOADS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_........AMX_Busy(%)", + "expression": "100 * ( [EXE.AMX_BUSY] / ( [CPU_CLK_UNHALTED.DISTRIBUTED] ) )" + }, + { + "name": "metric_TMA_......Ports_Utilized_1(%)", + "expression": "100 * ( [EXE_ACTIVITY.1_PORTS_UTIL] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Ports_Utilized_2(%)", + "expression": "100 * ( [EXE_ACTIVITY.2_PORTS_UTIL] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_......Ports_Utilized_3m(%)", + "expression": "100 * ( [UOPS_EXECUTED.CYCLES_GE_3] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_Retiring(%)", + "expression": "100 * ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_..Light_Operations(%)", + "expression": "100 * ( max( 0 , ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) - ( [PERF_METRICS.HEAVY_OPERATIONS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) ) )" + }, + { + "name": "metric_TMA_........FP_Vector_256b(%)", + "expression": "100 * ( min( ( ( [FP_ARITH_INST_RETIRED.256B_PACKED_DOUBLE] + [FP_ARITH_INST_RETIRED.256B_PACKED_SINGLE] + [FP_ARITH_INST_RETIRED2.256B_PACKED_HALF] ) / ( ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * ( [TOPDOWN.SLOTS] ) ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_........FP_Vector_512b(%)", + "expression": "100 * ( min( ( ( [FP_ARITH_INST_RETIRED.512B_PACKED_DOUBLE] + [FP_ARITH_INST_RETIRED.512B_PACKED_SINGLE] + [FP_ARITH_INST_RETIRED2.512B_PACKED_HALF] ) / ( ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * ( [TOPDOWN.SLOTS] ) ) ) , ( 1 ) ) )" + }, + { + "name": "metric_TMA_......Int_Vector_256b(%)", + "expression": "100 * ( ( [INT_VEC_RETIRED.ADD_256] + [INT_VEC_RETIRED.MUL_256] + [INT_VEC_RETIRED.VNNI_256] ) / ( ( [PERF_METRICS.RETIRING] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) ) * ( [TOPDOWN.SLOTS] ) ) )" + }, + { + "name": "metric_TMA_..Heavy_Operations(%)", + "expression": "100 * ( [PERF_METRICS.HEAVY_OPERATIONS] / ( [PERF_METRICS.FRONTEND_BOUND] + [PERF_METRICS.BAD_SPECULATION] + [PERF_METRICS.RETIRING] + [PERF_METRICS.BACKEND_BOUND] ) )" + }, + { + "name": "metric_TMA_....Microcode_Sequencer(%)", + "expression": "100 * ( [UOPS_RETIRED.MS] / ( [TOPDOWN.SLOTS] ) )" + }, + { + "name": "metric_TMA_Info_Thread_IPC", + "expression": "[instructions] / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_TMA_Info_System_SMT_2T_Utilization", + "expression": "(1 - [CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE] / [CPU_CLK_UNHALTED.REF_DISTRIBUTED]) if [SOCKET_COUNT] > 1 else 0", + "origin": "perfspect" + } +] \ No newline at end of file diff --git a/cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr_nofixedtma.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr_nofixedtma.json new file mode 100644 index 0000000..587d6b1 --- /dev/null +++ b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/spr_nofixedtma.json @@ -0,0 +1,349 @@ +[ + { + "name": "metric_CPU operating frequency (in GHz)", + "expression": "(([cpu-cycles] / [ref-cycles] * [SYSTEM_TSC_FREQ]) / 1000000000)" + }, + { + "name": "metric_CPU utilization %", + "expression": "100 * [ref-cycles] / [TSC]" + }, + { + "name": "metric_CPU utilization% in kernel mode", + "expression": "100 * [ref-cycles:k] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_CPI", + "name-txn": "metric_cycles per txn", + "expression": "[cpu-cycles] / [instructions]", + "expression-txn": "[cpu-cycles] / [TXN]" + }, + { + "name": "metric_kernel_CPI", + "name-txn": "metric_kernel_cycles per txn", + "expression": "[cpu-cycles:k] / [instructions:k]", + "expression-txn": "[cpu-cycles:k] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_IPC", + "name-txn": "metric_txn per cycle", + "expression": "[instructions] / [cpu-cycles]", + "expression-txn": "[TXN] / [cpu-cycles]", + "origin": "perfspect" + }, + { + "name": "metric_giga_instructions_per_sec", + "expression": "[instructions] / 1000000000", + "origin": "perfspect" + }, + { + "name": "metric_locks retired per instr", + "name-txn": "metric_locks retired per txn", + "expression": "[MEM_INST_RETIRED.LOCK_LOADS] / [instructions]", + "expression-txn": "[MEM_INST_RETIRED.LOCK_LOADS] / [TXN]", + "origin": "perfmon website" + }, + { + "name": "metric_L1D MPI (includes data+rfo w/ prefetches)", + "name-txn": "metric_L1D misses per txn (includes data+rfo w/ prefetches)", + "expression": "[L1D.REPLACEMENT] / [instructions]", + "expression-txn": "[L1D.REPLACEMENT] / [TXN]" + }, + { + "name": "metric_L1D demand data read hits per instr", + "name-txn": "metric_L1D demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L1_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L1_HIT] / [TXN]" + }, + { + "name": "metric_L1-I code read misses (w/ prefetches) per instr", + "name-txn": "metric_L1I code read misses (includes prefetches) per txn", + "expression": "[L2_RQSTS.ALL_CODE_RD] / [instructions]", + "expression-txn": "[L2_RQSTS.ALL_CODE_RD] / [TXN]" + }, + { + "name": "metric_L2 demand data read hits per instr", + "name-txn": "metric_L2 demand data read hits per txn", + "expression": "[MEM_LOAD_RETIRED.L2_HIT] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L2_HIT] / [TXN]" + }, + { + "name": "metric_L2 MPI (includes code+data+rfo w/ prefetches)", + "name-txn": "metric_L2 misses per txn (includes code+data+rfo w/ prefetches)", + "expression": "[L2_LINES_IN.ALL] / [instructions]", + "expression-txn": "[L2_LINES_IN.ALL] / [TXN]" + }, + { + "name": "metric_L2 demand data read MPI", + "name-txn": "metric_L2 demand data read misses per txn", + "expression": "[MEM_LOAD_RETIRED.L2_MISS] / [instructions]", + "expression-txn": "[MEM_LOAD_RETIRED.L2_MISS] / [TXN]" + }, + { + "name": "metric_L2 demand code MPI", + "name-txn": "metric_L2 demand code misses per txn", + "expression": "[L2_RQSTS.CODE_RD_MISS] / [instructions]", + "expression-txn": "[L2_RQSTS.CODE_RD_MISS] / [TXN]" + }, + { + "name": "metric_LLC code read MPI (demand+prefetch)", + "name-txn": "metric_LLC code read (demand+prefetch) misses per txn", + "expression": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [instructions]", + "expression-txn": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [TXN]" + }, + { + "name": "metric_LLC data read MPI (demand+prefetch)", + "name-txn": "metric_LLC data read (demand+prefetch) misses per txn", + "expression": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [instructions]", + "expression-txn": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [TXN]" + }, + { + "name": "metric_LLC total HITM (per instr) (excludes LLC prefetches)", + "name-txn": "metric_LLC total HITM per txn (excludes LLC prefetches)", + "expression": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM] / [instructions]", + "expression-txn": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HITM] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_LLC total HIT clean line forwards (per instr) (excludes LLC prefetches)", + "name-txn": "metric_LLC total HIT clean line forwards per txn (excludes LLC prefetches)", + "expression": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD] / [instructions]", + "expression-txn": "[OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_HIT_WITH_FWD] / [TXN]", + "origin": "perfspect" + }, + { + "name": "metric_Average LLC demand data read miss latency (in ns)", + "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" + }, + { + "name": "metric_Average LLC demand data read miss latency for LOCAL requests (in ns)", + "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" + }, + { + "name": "metric_Average LLC demand data read miss latency for REMOTE requests (in ns)", + "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" + }, + { + "name": "metric_UPI Data transmit BW (MB/sec) (only data)", + "expression": "([UNC_UPI_TxL_FLITS.ALL_DATA] * (64 / 9.0) / 1000000) / 1" + }, + { + "name": "metric_package power (watts)", + "expression": "[power/energy-pkg/]", + "origin": "perfspect" + }, + { + "name": "metric_DRAM power (watts)", + "expression": "[power/energy-ram/]", + "origin": "perfspect" + }, + { + "name": "metric_core c6 residency %", + "expression": "100 * [cstate_core/c6-residency/] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_package c6 residency %", + "expression": "100 * [cstate_pkg/c6-residency/] * [CORES_PER_SOCKET] / [TSC]", + "origin": "perfspect" + }, + { + "name": "metric_% Uops delivered from decoded Icache (DSB)", + "expression": "100 * ([IDQ.DSB_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" + }, + { + "name": "metric_% Uops delivered from legacy decode pipeline (MITE)", + "expression": "100 * ([IDQ.MITE_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" + }, + { + "name": "metric_core initiated local dram read bandwidth (MB/sec)", + "expression": "([OCR.READS_TO_CORE.LOCAL_DRAM] + [OCR.HWPF_L3.L3_MISS_LOCAL]) * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_core initiated remote dram read bandwidth (MB/sec)", + "expression": "([OCR.READS_TO_CORE.REMOTE_DRAM] + [OCR.HWPF_L3.REMOTE]) * 64 / 1000000", + "origin": "perfspect" + }, + { + "name": "metric_memory bandwidth read (MB/sec)", + "expression": "([UNC_M_CAS_COUNT.RD] * 64 / 1000000) / 1" + }, + { + "name": "metric_memory bandwidth write (MB/sec)", + "expression": "([UNC_M_CAS_COUNT.WR] * 64 / 1000000) / 1" + }, + { + "name": "metric_memory bandwidth total (MB/sec)", + "expression": "(([UNC_M_CAS_COUNT.RD] + [UNC_M_CAS_COUNT.WR]) * 64 / 1000000) / 1" + }, + { + "name": "metric_ITLB (2nd level) MPI", + "name-txn": "metric_ITLB (2nd level) misses per txn", + "expression": "[ITLB_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[ITLB_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) load MPI", + "name-txn": "metric_DTLB (2nd level) load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) 2MB large page load MPI", + "name-txn": "metric_DTLB (2nd level) 2MB large page load misses per txn", + "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [instructions]", + "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M] / [TXN]" + }, + { + "name": "metric_DTLB (2nd level) store MPI", + "name-txn": "metric_DTLB (2nd level) store misses per txn", + "expression": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [instructions]", + "expression-txn": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [TXN]" + }, + { + "name": "metric_NUMA %_Reads addressed to local DRAM", + "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" + }, + { + "name": "metric_NUMA %_Reads addressed to remote DRAM", + "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" + }, + { + "name": "metric_uncore frequency GHz", + "expression": "([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) / 1000000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_writes (MB/sec)", + "expression": "([UNC_CHA_TOR_INSERTS.IO_PCIRDCUR] * 64 / 1000000) / 1" + }, + { + "name": "metric_IO_bandwidth_disk_or_network_reads (MB/sec)", + "expression": "(([UNC_CHA_TOR_INSERTS.IO_ITOM] + [UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR]) * 64 / 1000000) / 1" + }, + { + "name": "metric_TMA_Frontend_Bound(%)", + "expression": "100 * ( ( [IDQ_UOPS_NOT_DELIVERED.CORE] - [INT_MISC.UOP_DROPPING] ) / ( [TOPDOWN.SLOTS_P] ) )" + }, + { + "name": "metric_TMA_..Fetch_Latency(%)", + "expression": "100 * ( ( [IDQ_BUBBLES.CYCLES_0_UOPS_DELIV.CORE] * ( 6 ) - [INT_MISC.UOP_DROPPING] ) / ( [TOPDOWN.SLOTS_P] ) )" + }, + { + "name": "metric_TMA_....ICache_Misses(%)", + "expression": "100 * ( [ICACHE_DATA.STALLS] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....ITLB_Misses(%)", + "expression": "100 * ( [ICACHE_TAG.STALLS] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....MS_Switches(%)", + "expression": "100 * ( ( 3 ) * [UOPS_RETIRED.MS:c1:e1] / ( [UOPS_RETIRED.SLOTS] / [UOPS_ISSUED.ANY] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....LCP(%)", + "expression": "100 * ( [DECODE.LCP] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....DSB_Switches(%)", + "expression": "100 * ( [DSB2MITE_SWITCHES.PENALTY_CYCLES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_..Fetch_Bandwidth(%)", + "expression": "100 * ( max( 0 , ( ( [IDQ_UOPS_NOT_DELIVERED.CORE] - [INT_MISC.UOP_DROPPING] ) / ( [TOPDOWN.SLOTS_P] ) ) - ( ( [IDQ_BUBBLES.CYCLES_0_UOPS_DELIV.CORE] * ( 6 ) - [INT_MISC.UOP_DROPPING] ) / ( [TOPDOWN.SLOTS_P] ) ) ) )" + }, + { + "name": "metric_TMA_....MITE(%)", + "expression": "100 * ( ( [IDQ.MITE_CYCLES_ANY] - [IDQ.MITE_CYCLES_OK] ) / ( [CPU_CLK_UNHALTED.DISTRIBUTED] ) / 2 )" + }, + { + "name": "metric_TMA_....DSB(%)", + "expression": "100 * ( ( [IDQ.DSB_CYCLES_ANY] - [IDQ.DSB_CYCLES_OK] ) / ( [CPU_CLK_UNHALTED.DISTRIBUTED] ) / 2 )" + }, + { + "name": "metric_TMA_Bad_Speculation(%)", + "expression": "100 * ( max( 1 - ( ( ( [IDQ_UOPS_NOT_DELIVERED.CORE] - [INT_MISC.UOP_DROPPING] ) / ( [TOPDOWN.SLOTS_P] ) ) + ( [TOPDOWN.BACKEND_BOUND_SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) + ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) ) , 0 ) )" + }, + { + "name": "metric_TMA_..Branch_Mispredicts(%)", + "expression": "100 * ( [TOPDOWN.BR_MISPREDICT_SLOTS] / ( [TOPDOWN.SLOTS_P] ) )" + }, + { + "name": "metric_TMA_..Machine_Clears(%)", + "expression": "100 * ( max( 0 , ( max( 1 - ( ( ( [IDQ_UOPS_NOT_DELIVERED.CORE] - [INT_MISC.UOP_DROPPING] ) / ( [TOPDOWN.SLOTS_P] ) ) + ( [TOPDOWN.BACKEND_BOUND_SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) + ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) ) , 0 ) ) - ( [TOPDOWN.BR_MISPREDICT_SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) ) )" + }, + { + "name": "metric_TMA_Backend_Bound(%)", + "expression": "100 * ( [TOPDOWN.BACKEND_BOUND_SLOTS] / ( [TOPDOWN.SLOTS_P] ) )" + }, + { + "name": "metric_TMA_..Memory_Bound(%)", + "expression": "100 * ( [TOPDOWN.MEMORY_BOUND_SLOTS] / ( [TOPDOWN.SLOTS_P] ) )" + }, + { + "name": "metric_TMA_....L1_Bound(%)", + "expression": "100 * ( max( ( [EXE_ACTIVITY.BOUND_ON_LOADS] - [MEMORY_ACTIVITY.STALLS_L1D_MISS] ) / ( [cpu-cycles] ) , 0 ) )" + }, + { + "name": "metric_TMA_....L2_Bound(%)", + "expression": "100 * ( ( [MEMORY_ACTIVITY.STALLS_L1D_MISS] - [MEMORY_ACTIVITY.STALLS_L2_MISS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....L3_Bound(%)", + "expression": "100 * ( ( [MEMORY_ACTIVITY.STALLS_L2_MISS] - [MEMORY_ACTIVITY.STALLS_L3_MISS] ) / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....DRAM_Bound(%)", + "expression": "100 * ( ( [MEMORY_ACTIVITY.STALLS_L3_MISS] / ( [cpu-cycles] ) ) )" + }, + { + "name": "metric_TMA_....Store_Bound(%)", + "expression": "100 * ( [EXE_ACTIVITY.BOUND_ON_STORES] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_..Core_Bound(%)", + "expression": "100 * ( max( 0 , ( [TOPDOWN.BACKEND_BOUND_SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) - ( [TOPDOWN.MEMORY_BOUND_SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) ) )" + }, + { + "name": "metric_TMA_....Divider(%)", + "expression": "100 * ( [ARITH.DIV_ACTIVE] / ( [cpu-cycles] ) )" + }, + { + "name": "metric_TMA_....AMX_Busy(%)", + "expression": "100 * ( [EXE.AMX_BUSY] / ( [CPU_CLK_UNHALTED.DISTRIBUTED] ) )" + }, + { + "name": "metric_TMA_Retiring(%)", + "expression": "100 * ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) )" + }, + { + "name": "metric_TMA_..Light_Operations(%)", + "expression": "100 * ( max( 0 , ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) - ( [UOPS_RETIRED.HEAVY] / ( [TOPDOWN.SLOTS_P] ) ) ) )" + }, + { + "name": "metric_TMA_....Memory_Operations(%)", + "expression": "100 * ( ( max( 0 , ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) - ( [UOPS_RETIRED.HEAVY] / ( [TOPDOWN.SLOTS_P] ) ) ) ) * [MEM_UOP_RETIRED.ANY] / ( ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) * ( [TOPDOWN.SLOTS_P] ) ) )" + }, + { + "name": "metric_TMA_....Fused_Instructions(%)", + "expression": "100 * ( ( max( 0 , ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) - ( [UOPS_RETIRED.HEAVY] / ( [TOPDOWN.SLOTS_P] ) ) ) ) * [INST_RETIRED.MACRO_FUSED] / ( ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) * ( [TOPDOWN.SLOTS_P] ) ) )" + }, + { + "name": "metric_TMA_....Non_Fused_Branches(%)", + "expression": "100 * ( ( max( 0 , ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) - ( [UOPS_RETIRED.HEAVY] / ( [TOPDOWN.SLOTS_P] ) ) ) ) * ( [BR_INST_RETIRED.ALL_BRANCHES] - [INST_RETIRED.MACRO_FUSED] ) / ( ( [UOPS_RETIRED.SLOTS] / ( [TOPDOWN.SLOTS_P] ) ) * ( [TOPDOWN.SLOTS_P] ) ) )" + }, + { + "name": "metric_TMA_..Heavy_Operations(%)", + "expression": "100 * ( [UOPS_RETIRED.HEAVY] / ( [TOPDOWN.SLOTS_P] ) )" + }, + { + "name": "metric_TMA_....Few_Uops_Instructions(%)", + "expression": "100 * ( max( 0 , ( [UOPS_RETIRED.HEAVY] / ( [TOPDOWN.SLOTS_P] ) ) - ( [UOPS_RETIRED.MS] / ( [TOPDOWN.SLOTS_P] ) ) ) )" + }, + { + "name": "metric_TMA_....Microcode_Sequencer(%)", + "expression": "100 * ( [UOPS_RETIRED.MS] / ( [TOPDOWN.SLOTS_P] ) )" + } +] \ No newline at end of file diff --git a/events/metric_srf.json b/cmd/metrics/resources/metrics/x86_64/GenuineIntel/srf.json similarity index 100% rename from events/metric_srf.json rename to cmd/metrics/resources/metrics/x86_64/GenuineIntel/srf.json diff --git a/cmd/metrics/summary.go b/cmd/metrics/summary.go new file mode 100644 index 0000000..9d1e809 --- /dev/null +++ b/cmd/metrics/summary.go @@ -0,0 +1,366 @@ +package metrics + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// functions to create summary (mean,min,max,stddev) metrics from metrics CSV + +import ( + "encoding/csv" + "encoding/json" + "fmt" + "io" + "math" + "os" + "strconv" + "strings" + + "perfspect/internal/util" +) + +// Summarize - generates formatted output from a CSV file containing metric values. +// The output can be in CSV or HTML format. Set html to true to generate HTML output otherwise CSV is generated. +func Summarize(csvInputPath string, html bool) (out string, err error) { + var metrics []metricsFromCSV + if metrics, err = newMetricsFromCSV(csvInputPath); err != nil { + return + } + if html { + if len(metrics) > 1 { + err = fmt.Errorf("html format is supported only when data's scope is '%s' or '%s' and granularity is '%s'", scopeSystem, scopeProcess, granularitySystem) + return + } + out, err = metrics[0].getHTML() + } else { + for i, m := range metrics { + var oneOut string + if oneOut, err = m.getCSV(i == 0); err != nil { + return + } + out += oneOut + } + } + return +} + +type metricStats struct { + mean float64 + min float64 + max float64 + stddev float64 +} + +type row struct { + timestamp float64 + socket string + cpu string + cgroup string + metrics map[string]float64 +} + +// newRow loads a row structure with given fields and field names +func newRow(fields []string, names []string) (r row, err error) { + r.metrics = make(map[string]float64) + for fIdx, field := range fields { + if fIdx == idxTimestamp { + var ts float64 + if ts, err = strconv.ParseFloat(field, 64); err != nil { + return + } + r.timestamp = ts + } else if fIdx == idxSocket { + r.socket = field + } else if fIdx == idxCPU { + r.cpu = field + } else if fIdx == idxCgroup { + r.cgroup = field + } else { + // metrics + var v float64 + if field != "" { + if v, err = strconv.ParseFloat(field, 64); err != nil { + return + } + } else { + v = math.NaN() + } + r.metrics[names[fIdx-idxFirstMetric]] = v + } + } + return +} + +const ( + idxTimestamp int = iota + idxSocket + idxCPU + idxCgroup + idxFirstMetric +) + +type metricsFromCSV struct { + names []string + rows []row + groupByField string + groupByValue string +} + +// newMetricsFromCSV - loads data from CSV. Returns a list of metrics, one per +// scope unit or granularity unit, e.g., one per socket, or one per PID +func newMetricsFromCSV(csvPath string) (metrics []metricsFromCSV, err error) { + var file *os.File + if file, err = os.Open(csvPath); err != nil { + return + } + reader := csv.NewReader(file) + groupByField := -1 + var groupByValues []string + var metricNames []string + var nonMetricNames []string + for idx := 0; true; idx++ { + var fields []string + if fields, err = reader.Read(); err != nil { + if err != io.EOF { + return + } + err = nil + } + if fields == nil { + // no more rows + break + } + if idx == 0 { + // headers + for fIdx, field := range fields { + if fIdx < idxFirstMetric { + nonMetricNames = append(nonMetricNames, field) + } else { + metricNames = append(metricNames, field) + } + } + continue + } + // Determine the scope and granularity of the captured data by looking + // at the first row of values. If none of these are set, then it's + // system scope and system granularity + if idx == 1 { + if fields[idxSocket] != "" { + groupByField = idxSocket + } else if fields[idxCPU] != "" { + groupByField = idxCPU + } else if fields[idxCgroup] != "" { + groupByField = idxCgroup + } + } + // Load row into a row structure + var r row + if r, err = newRow(fields, metricNames); err != nil { + return + } + // put the row into the associated list based on groupByField + if groupByField == -1 { // system scope/granularity + if len(metrics) == 0 { + metrics = append(metrics, metricsFromCSV{}) + metrics[0].names = metricNames + } + metrics[0].rows = append(metrics[0].rows, r) + } else { + groupByValue := fields[groupByField] + var listIdx int + if listIdx, err = util.StringIndexInList(groupByValue, groupByValues); err != nil { + groupByValues = append(groupByValues, groupByValue) + metrics = append(metrics, metricsFromCSV{}) + listIdx = len(metrics) - 1 + metrics[listIdx].names = metricNames + if groupByField == idxSocket { + metrics[listIdx].groupByField = nonMetricNames[idxSocket] + } else if groupByField == idxCPU { + metrics[listIdx].groupByField = nonMetricNames[idxCPU] + } else if groupByField == idxCgroup { + metrics[listIdx].groupByField = nonMetricNames[idxCgroup] + } + metrics[listIdx].groupByValue = groupByValue + } + metrics[listIdx].rows = append(metrics[listIdx].rows, r) + } + } + return +} + +// getStats - calculate summary stats (min, max, mean, stddev) for each metric +func (m *metricsFromCSV) getStats() (stats map[string]metricStats, err error) { + stats = make(map[string]metricStats) + for _, metricName := range m.names { + min := math.NaN() + max := math.NaN() + mean := math.NaN() + stddev := math.NaN() + count := 0 + sum := 0.0 + for _, row := range m.rows { + val := row.metrics[metricName] + if math.IsNaN(val) { + continue + } + if math.IsNaN(min) { // min was initialized to NaN + // first non-NaN value, so initialize + min = math.MaxFloat64 + max = 0 + sum = 0 + } + if val < min { + min = val + } + if val > max { + max = val + } + sum += val + count++ + } + // must be at least one valid value for this metric to calculate mean and standard deviation + if count > 0 { + mean = sum / float64(count) + distanceSquaredSum := 0.0 + for _, row := range m.rows { + val := row.metrics[metricName] + if math.IsNaN(val) { + continue + } + distance := mean - val + squared := distance * distance + distanceSquaredSum += squared + } + stddev = math.Sqrt(distanceSquaredSum / float64(count)) + } + stats[metricName] = metricStats{mean: mean, min: min, max: max, stddev: stddev} + } + return +} + +// getHTML - generate a string containing HTML representing the metrics +func (m *metricsFromCSV) getHTML() (html string, err error) { + var stats map[string]metricStats + if stats, err = m.getStats(); err != nil { + return + } + var htmlTemplate []byte + if htmlTemplate, err = resources.ReadFile("resources/base.html"); err != nil { + return + } + html = string(htmlTemplate) + html = strings.Replace(html, "TRANSACTIONS", "false", 1) // no transactions for now + + // hack to determine the architecture of the metrics source + var archIndex int + if _, ok := stats["metric_Macro-ops Retired"]; ok { // a metric that only exists in the AMD metric definitions + archIndex = 1 + } else { + archIndex = 0 + } + + type tmplReplace struct { + tmplVar string + metricNames []string + } + + // TMA Tab + templateReplace := []tmplReplace{ + {"FRONTEND", []string{"TMA_Frontend_Bound(%)", "Pipeline Utilization - Frontend Bound (%)"}}, + {"BACKEND", []string{"TMA_Backend_Bound(%)", "Pipeline Utilization - Backend Bound (%)"}}, + {"COREDATA", []string{"TMA_..Core_Bound(%)", "Pipeline Utilization - Backend Bound - CPU (%)"}}, + {"MEMORY", []string{"TMA_..Memory_Bound(%)", "Pipeline Utilization - Backend Bound - Memory (%)"}}, + {"BADSPECULATION", []string{"TMA_Bad_Speculation(%), Pipeline Utilization - Bad Speculation (%)"}}, + {"RETIRING", []string{"TMA_Retiring(%)", "Pipeline Utilization - Retiring (%)"}}, + } + haveTMA := false + if archIndex == 0 && !math.IsNaN(stats["TMA_Frontend_Bound(%)"].mean) { + haveTMA = true + } else if archIndex == 1 && !math.IsNaN(stats["Pipeline Utilization - Frontend Bound (%)"].mean) { + haveTMA = true + } + if haveTMA { + for _, tmpl := range templateReplace { + html = strings.Replace(html, tmpl.tmplVar, fmt.Sprintf("%f", stats[tmpl.metricNames[archIndex]].mean), -1) + } + } else { + for _, tmpl := range templateReplace { + html = strings.Replace(html, tmpl.tmplVar, "0", -1) + } + } + + templateReplace = []tmplReplace{ + // CPU Tab + {"CPUUTIL", []string{"CPU utilization %", "CPU utilization %"}}, + {"CPIDATA", []string{"CPI", "CPI"}}, + {"CPUFREQ", []string{"CPU operating frequency (in GHz)", "CPU operating frequency (in GHz)"}}, + // Memory Tab + {"L1DATA", []string{"L1D MPI (includes data+rfo w/ prefetches)", ""}}, + {"L2DATA", []string{"L2 MPI (includes code+data+rfo w/ prefetches)", ""}}, + {"LLCDATA", []string{"LLC data read MPI (demand+prefetch)", ""}}, + {"READDATA", []string{"memory bandwidth read (MB/sec)", "DRAM read bandwidth for local processor"}}, + {"WRITEDATA", []string{"memory bandwidth write (MB/sec)", "DRAM write bandwidth for local processor"}}, + {"TOTALDATA", []string{"memory bandwidth total (MB/sec)", ""}}, + {"REMOTENUMA", []string{"NUMA %_Reads addressed to remote DRAM", ""}}, + // Power Tab + {"PKGPOWER", []string{"package power (watts)", "package power (watts)"}}, + {"DRAMPOWER", []string{"DRAM power (watts)", ""}}, + } + for _, tmpl := range templateReplace { + var series [][]float64 + var firstTimestamp float64 + for rIdx, row := range m.rows { + if rIdx == 0 { + firstTimestamp = row.timestamp + } + if math.IsNaN(row.metrics[tmpl.metricNames[archIndex]]) { + continue + } + series = append(series, []float64{row.timestamp - firstTimestamp, row.metrics[tmpl.metricNames[archIndex]]}) + } + var seriesBytes []byte + if seriesBytes, err = json.Marshal(series); err != nil { + return + } + html = strings.Replace(html, tmpl.tmplVar, string(seriesBytes), -1) + } + // All Metrics Tab + var metricHTMLStats [][]string + for _, name := range m.names { + metricHTMLStats = append(metricHTMLStats, []string{ + name, + fmt.Sprintf("%f", stats[name].mean), + fmt.Sprintf("%f", stats[name].min), + fmt.Sprintf("%f", stats[name].max), + fmt.Sprintf("%f", stats[name].stddev), + }) + } + var jsonMetricsBytes []byte + if jsonMetricsBytes, err = json.Marshal(metricHTMLStats); err != nil { + return + } + jsonMetrics := string(jsonMetricsBytes) + html = strings.Replace(html, "ALLMETRICS", string(jsonMetrics), -1) + return +} + +// getCSV - generate CSV string representing the summary statistics of the metrics +func (m *metricsFromCSV) getCSV(includeFieldNames bool) (out string, err error) { + var stats map[string]metricStats + if stats, err = m.getStats(); err != nil { + return + } + if includeFieldNames { + out = "metric,mean,min,max,stddev\n" + if m.groupByField != "" { + out = m.groupByField + "," + out + } + } + for _, name := range m.names { + if m.groupByValue == "" { + out += fmt.Sprintf("%s,%f,%f,%f,%f\n", name, stats[name].mean, stats[name].min, stats[name].max, stats[name].stddev) + } else { + out += fmt.Sprintf("%s,%s,%f,%f,%f,%f\n", m.groupByValue, name, stats[name].mean, stats[name].min, stats[name].max, stats[name].stddev) + } + } + return +} diff --git a/cmd/report/report.go b/cmd/report/report.go new file mode 100644 index 0000000..4ddacd0 --- /dev/null +++ b/cmd/report/report.go @@ -0,0 +1,508 @@ +// Package report is a subcommand of the root command. It generates a configuration report for target(s). +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "log/slog" + "os" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/xuri/excelize/v2" + + "perfspect/internal/common" + "perfspect/internal/report" + "perfspect/internal/script" + "perfspect/internal/util" +) + +const cmdName = "report" + +var examples = []string{ + fmt.Sprintf(" Data from local host: $ %s %s", common.AppName, cmdName), + fmt.Sprintf(" Specific data from local host: $ %s %s --bios --os --cpu --format html,json", common.AppName, cmdName), + fmt.Sprintf(" All data from remote target: $ %s %s --target 192.168.1.1 --user fred --key fred_key", common.AppName, cmdName), + fmt.Sprintf(" Run all benchmarks: $ %s %s --benchmark all", common.AppName, cmdName), + fmt.Sprintf(" Run specific benchmarks: $ %s %s --benchmark speed,power", common.AppName, cmdName), + fmt.Sprintf(" Data from multiple targets: $ %s %s --targets targets.yaml", common.AppName, cmdName), +} + +var Cmd = &cobra.Command{ + Use: cmdName, + Short: "Generate configuration report for target(s)", + Example: strings.Join(examples, "\n"), + RunE: runCmd, + PreRunE: validateFlags, + GroupID: "primary", + Args: cobra.NoArgs, + SilenceErrors: true, +} + +// flag vars +var ( + flagAll bool + // categories + flagHost bool + flagPcie bool + flagBios bool + flagOs bool + flagSoftware bool + flagCpu bool + flagIsa bool + flagAccelerator bool + flagPower bool + flagCstates bool + flagTurbo bool + flagUncore bool + flagElc bool + flagMemory bool + flagDimm bool + flagNic bool + flagNetIrq bool + flagDisk bool + flagFilesystem bool + flagGpu bool + flagGaudi bool + flagCxl bool + flagCve bool + flagProcess bool + flagSensor bool + flagChassisStatus bool + flagPmu bool + flagSystemEventLog bool + flagKernelLog bool + flagSystemSummary bool + + flagBenchmark []string +) + +// flag names +const ( + flagAllName = "all" + // categories + flagHostName = "host" + flagPcieName = "pcie" + flagBiosName = "bios" + flagOsName = "os" + flagSoftwareName = "software" + flagCpuName = "cpu" + flagIsaName = "isa" + flagAcceleratorName = "accelerator" + flagPowerName = "power" + flagCstatesName = "cstates" + flagTurboName = "turbo" + flagUncoreName = "uncore" + flagElcName = "elc" + flagMemoryName = "memory" + flagDimmName = "dimm" + flagNicName = "nic" + flagNetIrqName = "netirq" + flagDiskName = "disk" + flagFilesystemName = "filesystem" + flagGpuName = "gpu" + flagGaudiName = "gaudi" + flagCxlName = "cxl" + flagCveName = "cve" + flagProcessName = "process" + flagSensorName = "sensor" + flagChassisStatusName = "chassisstatus" + flagPmuName = "pmu" + flagSystemEventLogName = "sel" + flagKernelLogName = "kernellog" + flagSystemSummaryName = "system-summary" + + flagBenchmarkName = "benchmark" +) + +var benchmarkOptions = []string{ + "speed", + "power", + "temperature", + "frequency", + "memory", + "numa", +} + +var benchmarkAll = "all" + +var benchmarkTableNames = map[string][]string{ + "speed": {report.CPUSpeedTableName}, + "power": {report.CPUPowerTableName}, + "temperature": {report.CPUTemperatureTableName}, + "frequency": {report.CPUFrequencyTableName}, + "memory": {report.MemoryLatencyTableName}, + "numa": {report.NUMABandwidthTableName}, +} + +var benchmarkSummaryTableName = "Benchmark Summary" + +// categories maps flag names to tables that will be included in report +var categories = []common.Category{ + {FlagName: flagHostName, FlagVar: &flagHost, Help: "Host", TableNames: []string{report.HostTableName}}, + {FlagName: flagBiosName, FlagVar: &flagBios, Help: "BIOS", TableNames: []string{report.BIOSTableName}}, + {FlagName: flagOsName, FlagVar: &flagOs, Help: "Operating System", TableNames: []string{report.OperatingSystemTableName}}, + {FlagName: flagSoftwareName, FlagVar: &flagSoftware, Help: "Software Versions", TableNames: []string{report.SoftwareVersionTableName}}, + {FlagName: flagCpuName, FlagVar: &flagCpu, Help: "Processor Details", TableNames: []string{report.CPUTableName}}, + {FlagName: flagIsaName, FlagVar: &flagIsa, Help: "Instruction Sets", TableNames: []string{report.ISATableName}}, + {FlagName: flagAcceleratorName, FlagVar: &flagAccelerator, Help: "On-board Accelerators", TableNames: []string{report.AcceleratorTableName}}, + {FlagName: flagPowerName, FlagVar: &flagPower, Help: "Power Settings", TableNames: []string{report.PowerTableName}}, + {FlagName: flagCstatesName, FlagVar: &flagCstates, Help: "C-states", TableNames: []string{report.CstateTableName}}, + {FlagName: flagTurboName, FlagVar: &flagTurbo, Help: "Turbo Frequency", TableNames: []string{report.CoreTurboFrequencyTableName}}, + {FlagName: flagUncoreName, FlagVar: &flagUncore, Help: "Uncore Configuration", TableNames: []string{report.UncoreTableName}}, + {FlagName: flagElcName, FlagVar: &flagElc, Help: "Efficiency Latency Control Settings", TableNames: []string{report.ElcTableName}}, + {FlagName: flagMemoryName, FlagVar: &flagMemory, Help: "Memory Configuration", TableNames: []string{report.MemoryTableName}}, + {FlagName: flagDimmName, FlagVar: &flagDimm, Help: "DIMM Population", TableNames: []string{report.DIMMTableName}}, + {FlagName: flagNicName, FlagVar: &flagNic, Help: "Network Cards", TableNames: []string{report.NICTableName}}, + {FlagName: flagNetIrqName, FlagVar: &flagNetIrq, Help: "Network IRQ to CPU Mapping", TableNames: []string{report.NetworkIRQMappingTableName}}, + {FlagName: flagDiskName, FlagVar: &flagDisk, Help: "Storage Devices", TableNames: []string{report.DiskTableName}}, + {FlagName: flagFilesystemName, FlagVar: &flagFilesystem, Help: "File Systems", TableNames: []string{report.FilesystemTableName}}, + {FlagName: flagGpuName, FlagVar: &flagGpu, Help: "GPUs", TableNames: []string{report.GPUTableName}}, + {FlagName: flagGaudiName, FlagVar: &flagGaudi, Help: "Gaudi Devices", TableNames: []string{report.GaudiTableName}}, + {FlagName: flagCxlName, FlagVar: &flagCxl, Help: "CXL Devices", TableNames: []string{report.CXLDeviceTableName}}, + {FlagName: flagPcieName, FlagVar: &flagPcie, Help: "PCIE Slots", TableNames: []string{report.PCIeSlotsTableName}}, + {FlagName: flagCveName, FlagVar: &flagCve, Help: "Vulnerabilities", TableNames: []string{report.CVETableName}}, + {FlagName: flagProcessName, FlagVar: &flagProcess, Help: "Process List", TableNames: []string{report.ProcessTableName}}, + {FlagName: flagSensorName, FlagVar: &flagSensor, Help: "Sensor Status", TableNames: []string{report.SensorTableName}}, + {FlagName: flagChassisStatusName, FlagVar: &flagChassisStatus, Help: "Chassis Status", TableNames: []string{report.ChassisStatusTableName}}, + {FlagName: flagPmuName, FlagVar: &flagPmu, Help: "Performance Monitoring Unit Status", TableNames: []string{report.PMUTableName}}, + {FlagName: flagSystemEventLogName, FlagVar: &flagSystemEventLog, Help: "System Event Log", TableNames: []string{report.SystemEventLogTableName}}, + {FlagName: flagKernelLogName, FlagVar: &flagKernelLog, Help: "Kernel Log", TableNames: []string{report.KernelLogTableName}}, + {FlagName: flagSystemSummaryName, FlagVar: &flagSystemSummary, Help: "System Summary", TableNames: []string{report.SystemSummaryTableName}}, +} + +func init() { + // set up category flags + for _, cat := range categories { + Cmd.Flags().BoolVar(cat.FlagVar, cat.FlagName, cat.DefaultValue, cat.Help) + } + // set up other flags + Cmd.Flags().StringVar(&common.FlagInput, common.FlagInputName, "", "") + Cmd.Flags().BoolVar(&flagAll, flagAllName, false, "") + Cmd.Flags().StringSliceVar(&common.FlagFormat, common.FlagFormatName, []string{report.FormatAll}, "") + Cmd.Flags().StringSliceVar(&flagBenchmark, flagBenchmarkName, []string{}, "") + + common.AddTargetFlags(Cmd) + + Cmd.SetUsageFunc(usageFunc) +} + +func usageFunc(cmd *cobra.Command) error { + cmd.Printf("Usage: %s [flags]\n\n", cmd.CommandPath()) + cmd.Printf("Examples:\n%s\n\n", cmd.Example) + cmd.Println("Flags:") + for _, group := range getFlagGroups() { + cmd.Printf(" %s:\n", group.GroupName) + for _, flag := range group.Flags { + flagDefault := "" + if cmd.Flags().Lookup(flag.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(flag.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", flag.Name, flag.Help, flagDefault) + } + } + cmd.Println("\nGlobal Flags:") + cmd.Parent().PersistentFlags().VisitAll(func(pf *pflag.Flag) { + flagDefault := "" + if cmd.Parent().PersistentFlags().Lookup(pf.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(pf.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", pf.Name, pf.Usage, flagDefault) + }) + return nil +} + +func getFlagGroups() []common.FlagGroup { + var groups []common.FlagGroup + flags := []common.Flag{ + { + Name: flagAllName, + Help: "report configuration for all categories", + }, + } + for _, cat := range categories { + flags = append(flags, common.Flag{ + Name: fmt.Sprintf(cat.FlagName), + Help: cat.Help, + }) + } + groups = append(groups, common.FlagGroup{ + GroupName: "Categories", + Flags: flags, + }) + flags = []common.Flag{ + { + Name: common.FlagFormatName, + Help: fmt.Sprintf("choose output format(s) from: %s", strings.Join(append([]string{report.FormatAll}, report.FormatOptions...), ", ")), + }, + { + Name: flagBenchmarkName, + Help: fmt.Sprintf("choose benchmark(s) to include in report from: %s", strings.Join(append([]string{benchmarkAll}, benchmarkOptions...), ", ")), + }, + } + groups = append(groups, common.FlagGroup{ + GroupName: "Other Options", + Flags: flags, + }) + groups = append(groups, common.GetTargetFlagGroup()) + flags = []common.Flag{ + { + Name: common.FlagInputName, + Help: "\".raw\" file, or directory containing \".raw\" files. Will skip data collection and use raw data for reports.", + }, + } + groups = append(groups, common.FlagGroup{ + GroupName: "Advanced Options", + Flags: flags, + }) + return groups +} + +func validateFlags(cmd *cobra.Command, args []string) error { + // set flagAll if all categories are selected or if none are selected + if !flagAll { + numCategoriesTrue := 0 + for _, cat := range categories { + if *cat.FlagVar { + numCategoriesTrue++ + break + } + } + if numCategoriesTrue == len(categories) || numCategoriesTrue == 0 { + flagAll = true + } + } + // validate format options + for _, format := range common.FlagFormat { + formatOptions := append([]string{report.FormatAll}, report.FormatOptions...) + if !util.StringInList(format, formatOptions) { + err := fmt.Errorf("format options are: %s", strings.Join(formatOptions, ", ")) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + } + // validate benchmark options + for _, benchmark := range flagBenchmark { + options := append([]string{benchmarkAll}, benchmarkOptions...) + if !util.StringInList(benchmark, options) { + err := fmt.Errorf("benchmark options are: %s", strings.Join(options, ", ")) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + } + // if benchmark all is selected, replace it with all benchmark options + if util.StringInList(benchmarkAll, flagBenchmark) { + flagBenchmark = benchmarkOptions + } + return nil +} + +func runCmd(cmd *cobra.Command, args []string) error { + tableNames := []string{} + for _, cat := range categories { + if (cat.FlagVar != nil && *cat.FlagVar) || (flagAll) { + for _, tableName := range cat.TableNames { + tableNames = util.UniqueAppend(tableNames, tableName) + } + } + } + // add benchmark tables + for _, benchmark := range flagBenchmark { + for _, tableName := range benchmarkTableNames[benchmark] { + tableNames = util.UniqueAppend(tableNames, tableName) + } + } + // include benchmark summary table if all benchmark options are selected + var summaryFunc common.SummaryFunc + if len(flagBenchmark) == len(benchmarkOptions) { + summaryFunc = benchmarkSummaryFromTableValues + } + // include insights table if all categories are selected + var insightsFunc common.InsightsFunc + if flagAll { + insightsFunc = common.DefaultInsightsFunc + } + reportingCommand := common.ReportingCommand{ + Cmd: cmd, + TableNames: tableNames, + SummaryFunc: summaryFunc, + SummaryTableName: benchmarkSummaryTableName, + InsightsFunc: insightsFunc, + } + return reportingCommand.Run() +} + +func benchmarkSummaryFromTableValues(allTableValues []report.TableValues, outputs map[string]script.ScriptOutput) report.TableValues { + tableValues := report.TableValues{ + TableDefinition: report.TableDefinition{ + Name: benchmarkSummaryTableName, + HasRows: false, + MenuLabel: benchmarkSummaryTableName, + HTMLTableRendererFunc: summaryHTMLTableRenderer, + XlsxTableRendererFunc: summaryXlsxTableRenderer, + TextTableRendererFunc: summaryTextTableRenderer, + }, + Fields: []report.Field{ + {Name: "CPU Speed", Values: []string{getValueFromTableValues(getTableValues(allTableValues, report.CPUSpeedTableName), "Ops/s", 0) + (" Ops/s")}}, + {Name: "Single-core Maximum frequency"}, + {Name: "All-core Maximum frequency"}, + {Name: "Maximum Power", Values: []string{getValueFromTableValues(getTableValues(allTableValues, report.CPUPowerTableName), "Maximum Power", 0)}}, + {Name: "Maximum Temperature", Values: []string{getValueFromTableValues(getTableValues(allTableValues, report.CPUTemperatureTableName), "Maximum Temperature", 0)}}, + {Name: "Minimum Power", Values: []string{getValueFromTableValues(getTableValues(allTableValues, report.CPUPowerTableName), "Minimum Power", 0)}}, + {Name: "Memory Peak Bandwidth"}, + {Name: "Memory Minimum Latency", Values: []string{getValueFromTableValues(getTableValues(allTableValues, report.MemoryLatencyTableName), "Latency (ns)", 0) + " ns"}}, + {Name: "Microarchitecture", Values: []string{getValueFromTableValues(getTableValues(allTableValues, report.SystemSummaryTableName), "Microarchitecture", 0)}}, + {Name: "Sockets", Values: []string{getValueFromTableValues(getTableValues(allTableValues, report.SystemSummaryTableName), "Sockets", 0)}}, + }, + } + // get the maximum frequencies from turbostat output + singleCore, allCore, _, _ := report.ParseTurbostatOutput(outputs[script.TurboFrequencyPowerAndTemperatureScriptName].Stdout) + tableValues.Fields[1].Values = []string{singleCore} + tableValues.Fields[2].Values = []string{allCore} + + // get the maximum memory bandwidth from the memory latency table + memLatTableValues := getTableValues(allTableValues, report.MemoryLatencyTableName) + var bandwidthValues []string + if len(memLatTableValues.Fields) > 1 { + bandwidthValues = getTableValues(allTableValues, report.MemoryLatencyTableName).Fields[1].Values + } + maxBandwidth := 0.0 + for _, bandwidthValue := range bandwidthValues { + bandwidth, err := strconv.ParseFloat(bandwidthValue, 64) + if err != nil { + slog.Error("unexpected value in memory bandwidth", slog.String("error", err.Error()), slog.Float64("value", bandwidth)) + break + } + if bandwidth > maxBandwidth { + maxBandwidth = bandwidth + } + } + if maxBandwidth != 0 { + tableValues.Fields[6].Values = []string{fmt.Sprintf("%.1f GB/s", maxBandwidth)} + } else { + tableValues.Fields[6].Values = []string{""} + } + return tableValues +} + +// getTableValues returns the table values for a table with a given name +func getTableValues(allTableValues []report.TableValues, tableName string) report.TableValues { + for _, tv := range allTableValues { + if tv.Name == tableName { + return tv + } + } + return report.TableValues{} +} + +// getValueFromTableValues returns the value of a field in a table +// if row is -1, it returns the last value +func getValueFromTableValues(tv report.TableValues, fieldName string, row int) string { + for _, fv := range tv.Fields { + if fv.Name == fieldName { + if row == -1 { // return the last value + if len(fv.Values) == 0 { + return "" + } + return fv.Values[len(fv.Values)-1] + } + if len(fv.Values) > row { + return fv.Values[row] + } + break + } + } + return "" +} + +// ReferenceData is a struct that holds reference data for a microarchitecture +type ReferenceData struct { + Description string + CPUSpeed float64 + SingleCoreFreq float64 + AllCoreFreq float64 + MaxPower float64 + MaxTemp float64 + MinPower float64 + MemPeakBandwidth float64 + MemMinLatency float64 +} + +// ReferenceDataKey is a struct that holds the key for reference data +type ReferenceDataKey struct { + Microarchitecture string + Sockets string +} + +// referenceData is a map of reference data for microarchitectures +var referenceData = map[ReferenceDataKey]ReferenceData{ + {"BDX", "2"}: {Description: "Reference (Intel 2S Xeon E5-2699 v4)", CPUSpeed: 403415, SingleCoreFreq: 3509, AllCoreFreq: 2980, MaxPower: 289.9, MaxTemp: 0, MinPower: 0, MemPeakBandwidth: 138.1, MemMinLatency: 78}, + {"SKX", "2"}: {Description: "Reference (Intel 2S Xeon 8180)", CPUSpeed: 585157, SingleCoreFreq: 3758, AllCoreFreq: 3107, MaxPower: 429.07, MaxTemp: 0, MinPower: 0, MemPeakBandwidth: 225.1, MemMinLatency: 71}, + {"CLX", "2"}: {Description: "Reference (Intel 2S Xeon 8280)", CPUSpeed: 548644, SingleCoreFreq: 3928, AllCoreFreq: 3926, MaxPower: 415.93, MaxTemp: 0, MinPower: 0, MemPeakBandwidth: 223.9, MemMinLatency: 72}, + {"ICX", "2"}: {Description: "Reference (Intel 2S Xeon 8380)", CPUSpeed: 933644, SingleCoreFreq: 3334, AllCoreFreq: 2950, MaxPower: 552, MaxTemp: 0, MinPower: 175.38, MemPeakBandwidth: 350.7, MemMinLatency: 70}, + {"SPR_XCC", "2"}: {Description: "Reference (Intel 2S Xeon 8480+)", CPUSpeed: 1678712, SingleCoreFreq: 3776, AllCoreFreq: 2996, MaxPower: 698.35, MaxTemp: 0, MinPower: 249.21, MemPeakBandwidth: 524.6, MemMinLatency: 111.8}, + {"SPR_XCC", "1"}: {Description: "Reference (Intel 1S Xeon 8480+)", CPUSpeed: 845743, SingleCoreFreq: 3783, AllCoreFreq: 2999, MaxPower: 334.68, MaxTemp: 0, MinPower: 163.79, MemPeakBandwidth: 264.0, MemMinLatency: 112.2}, + {"EMR_XCC", "2"}: {Description: "Reference (Intel 2S Xeon 8592V)", CPUSpeed: 1789534, SingleCoreFreq: 3862, AllCoreFreq: 2898, MaxPower: 664.4, MaxTemp: 0, MinPower: 166.36, MemPeakBandwidth: 553.5, MemMinLatency: 92.0}, + {"SRF", "2"}: {Description: "Reference (Intel 2S Xeon 6780E)", CPUSpeed: 2524191, SingleCoreFreq: 2997, AllCoreFreq: 2701, MaxPower: 545.10, MaxTemp: 0, MinPower: 0, MemPeakBandwidth: 540.3, MemMinLatency: 125.93}, +} + +// getFieldIndex returns the index of a field in a list of fields +func getFieldIndex(fields []report.Field, fieldName string) (int, error) { + for i, field := range fields { + if field.Name == fieldName { + return i, nil + } + } + return -1, fmt.Errorf("field not found: %s", fieldName) +} + +// summaryHTMLTableRenderer is a custom HTML table renderer for the summary table +// it removes the Microarchitecture and Sockets fields and adds a reference table +func summaryHTMLTableRenderer(tv report.TableValues, targetName string) string { + uarchFieldIdx, err := getFieldIndex(tv.Fields, "Microarchitecture") + if err != nil { + panic(err) + } + socketsFieldIdx, err := getFieldIndex(tv.Fields, "Sockets") + if err != nil { + panic(err) + } + // if we have reference data that matches the microarchitecture and sockets, use it + if refData, ok := referenceData[ReferenceDataKey{tv.Fields[uarchFieldIdx].Values[0], tv.Fields[socketsFieldIdx].Values[0]}]; ok { + // remove microarchitecture and sockets fields + fields := tv.Fields[:len(tv.Fields)-2] + refTableValues := report.TableValues{ + Fields: []report.Field{ + {Name: "CPU Speed", Values: []string{fmt.Sprintf("%.0f Ops/s", refData.CPUSpeed)}}, + {Name: "Single-core Maximum frequency", Values: []string{fmt.Sprintf("%.0f MHz", refData.SingleCoreFreq)}}, + {Name: "All-core Maximum frequency", Values: []string{fmt.Sprintf("%.0f MHz", refData.AllCoreFreq)}}, + {Name: "Maximum Power", Values: []string{fmt.Sprintf("%.0f W", refData.MaxPower)}}, + {Name: "Maximum Temperature", Values: []string{fmt.Sprintf("%.0f C", refData.MaxTemp)}}, + {Name: "Minimum Power", Values: []string{fmt.Sprintf("%.0f W", refData.MinPower)}}, + {Name: "Memory Peak Bandwidth", Values: []string{fmt.Sprintf("%.0f GB/s", refData.MemPeakBandwidth)}}, + {Name: "Memory Minimum Latency", Values: []string{fmt.Sprintf("%.0f ns", refData.MemMinLatency)}}, + }, + } + return report.RenderMultiTargetTableValuesAsHTML([]report.TableValues{{TableDefinition: tv.TableDefinition, Fields: fields}, refTableValues}, []string{targetName, refData.Description}) + } else { + // remove microarchitecture and sockets fields + fields := tv.Fields[:len(tv.Fields)-2] + return report.DefaultHTMLTableRendererFunc(report.TableValues{TableDefinition: tv.TableDefinition, Fields: fields}) + } +} + +func summaryXlsxTableRenderer(tv report.TableValues, f *excelize.File, targetName string, row *int) { + // remove microarchitecture and sockets fields + fields := tv.Fields[:len(tv.Fields)-2] + report.DefaultXlsxTableRendererFunc(report.TableValues{TableDefinition: tv.TableDefinition, Fields: fields}, f, report.XlsxPrimarySheetName, row) +} + +func summaryTextTableRenderer(tv report.TableValues) string { + // remove microarchitecture and sockets fields + fields := tv.Fields[:len(tv.Fields)-2] + return report.DefaultTextTableRendererFunc(report.TableValues{TableDefinition: tv.TableDefinition, Fields: fields}) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..4dc3b8d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,431 @@ +// Package cmd provides the command line interface for the application. +package cmd + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log/slog" + "net" + "net/http" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" + + "perfspect/cmd/config" + "perfspect/cmd/flame" + "perfspect/cmd/metrics" + "perfspect/cmd/report" + "perfspect/cmd/telemetry" + "perfspect/internal/common" + "perfspect/internal/util" + + "github.com/spf13/cobra" +) + +var gLogFile *os.File +var gVersion = "9.9.9" // overwritten by ldflags in Makefile, set to high number here to avoid update prompt while debugging + +const ( + // LongAppName is the name of the application + LongAppName = "PerfSpect" +) + +var examples = []string{ + fmt.Sprintf(" Generate a configuration report: $ %s report", common.AppName), + fmt.Sprintf(" Monitor micro-architectural metrics: $ %s metrics", common.AppName), + fmt.Sprintf(" Generate a configuration report on a remote target: $ %s report --target 192.168.1.2 --user elaine --key ~/.ssh/id_rsa", common.AppName), + fmt.Sprintf(" Generate configuration reports for multiple remote targets: $ %s report --targets ./targets.yaml", common.AppName), +} + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: common.AppName, + Short: common.AppName, + Long: fmt.Sprintf(`%s (%s) is a multi-function utility for performance engineers analyzing software running on Intel Xeon platforms.`, LongAppName, common.AppName), + Example: strings.Join(examples, "\n"), + PersistentPreRunE: initializeApplication, // will only be run if command has a 'Run' function + PersistentPostRunE: terminateApplication, // ... + Version: gVersion, +} + +var ( + // logging + flagDebug bool + flagSyslog bool + // output + flagOutputDir string + flagTempDir string + flagNoCheckUpdate bool +) + +const ( + flagDebugName = "debug" + flagSyslogName = "syslog" + flagOutputDirName = "output" + flagTempDirName = "tempdir" + flagNoCheckUpdateName = "noupdate" +) + +func init() { + rootCmd.SetUsageTemplate(`Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command] [flags]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{.Example}}{{end}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}} + +Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} + +{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +Global Flags: +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}} +`) + rootCmd.SetHelpCommand(&cobra.Command{}) // block the help command + rootCmd.CompletionOptions.HiddenDefaultCmd = true + rootCmd.AddGroup([]*cobra.Group{{ID: "primary", Title: "Commands:"}}...) + rootCmd.AddCommand(report.Cmd) + rootCmd.AddCommand(metrics.Cmd) + rootCmd.AddCommand(telemetry.Cmd) + rootCmd.AddCommand(flame.Cmd) + rootCmd.AddCommand(config.Cmd) + if onIntelNetwork() { + rootCmd.AddGroup([]*cobra.Group{{ID: "other", Title: "Other Commands:"}}...) + rootCmd.AddCommand(updateCmd) + } + // Global (persistent) flags + rootCmd.PersistentFlags().BoolVar(&flagDebug, flagDebugName, false, "enable debug logging") + rootCmd.PersistentFlags().BoolVar(&flagSyslog, flagSyslogName, false, "log to syslog") + rootCmd.PersistentFlags().StringVar(&flagOutputDir, flagOutputDirName, "", "override the output directory") + rootCmd.PersistentFlags().StringVar(&flagTempDir, flagTempDirName, "", "override the local temp directory") + rootCmd.PersistentFlags().BoolVar(&flagNoCheckUpdate, flagNoCheckUpdateName, false, "skip application update check") +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + cobra.EnableCommandSorting = false + cobra.EnableCaseInsensitive = true + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func initializeApplication(cmd *cobra.Command, args []string) error { + var err error + // verify requested output directory exists or create an output directory + var outputDir string + if flagOutputDir != "" { + var err error + outputDir, err = util.AbsPath(flagOutputDir) + if err != nil { + fmt.Printf("Error: failed to expand output dir %v\n", err) + os.Exit(1) + } + exists, err := util.DirectoryExists(outputDir) + if err != nil { + fmt.Printf("Error: failed to determine if output dir exists: %v\n", err) + os.Exit(1) + } + if !exists { + fmt.Printf("Error: requested output dir, %s, does not exist\n", outputDir) + os.Exit(1) + } + } else { + // set output dir path to app name + timestamp (dont' create the directory) + outputDirName := common.AppName + "_" + time.Now().Local().Format("2006-01-02_15-04-05") + var err error + // outputDir will be in current working directory + outputDir, err = util.AbsPath(outputDirName) + if err != nil { + fmt.Printf("Error: failed to expand output dir %v\n", err) + os.Exit(1) + } + } + // open log file in current directory + gLogFile, err = os.OpenFile(common.AppName+".log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + fmt.Printf("Error: failed to open log file: %v\n", err) + os.Exit(1) + } + var logLevel slog.Leveler + var logSource bool + if flagDebug { + logLevel = slog.LevelDebug + logSource = true + } else { + logLevel = slog.LevelInfo + logSource = false + } + opts := &slog.HandlerOptions{ + Level: logLevel, + AddSource: logSource, + } + logger := slog.New(slog.NewTextHandler(gLogFile, opts)) + slog.SetDefault(logger) + slog.Info("Starting up", slog.String("app", common.AppName), slog.String("version", gVersion), slog.Int("PID", os.Getpid()), slog.String("arguments", strings.Join(os.Args, " "))) + // verify requested local temp dir exists + var localTempDir string + if flagTempDir != "" { + localTempDir, err = util.AbsPath(flagTempDir) + if err != nil { + fmt.Printf("Error: failed to expand temp dir path: %v\n", err) + os.Exit(1) + } + exists, err := util.DirectoryExists(localTempDir) + if err != nil { + fmt.Printf("Error: failed to determine if temp dir path exists: %v\n", err) + os.Exit(1) + } + if !exists { + fmt.Printf("Error: requested temp dir, %s, does not exist\n", localTempDir) + os.Exit(1) + } + } else { + localTempDir = os.TempDir() + } + applicationTempDir, err := os.MkdirTemp(localTempDir, fmt.Sprintf("%s.tmp.", common.AppName)) + if err != nil { + fmt.Printf("Error: failed to create temp dir: %v\n", err) + os.Exit(1) + } + cmd.SetContext( + context.WithValue( + context.Background(), + common.AppContext{}, + common.AppContext{ + OutputDir: outputDir, + TempDir: applicationTempDir, + Version: gVersion}, + ), + ) + // check for updates unless the user has disabled this feature or is not on the Intel network or is running the update command + if !flagNoCheckUpdate && onIntelNetwork() && cmd.Name() != "update" { + // catch signals to allow for graceful shutdown + sigChannel := make(chan os.Signal, 1) + signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-sigChannel + slog.Info("received signal", slog.String("signal", sig.String())) + terminateApplication(cmd, args) + fmt.Println() + os.Exit(1) + }() + defer signal.Stop(sigChannel) + slog.Info("Checking for updates") + updateAvailable, latestManifest, err := checkForUpdates(gVersion) + if err != nil { + slog.Error(err.Error()) + } else if updateAvailable { + fmt.Fprintf(os.Stderr, "A new version (%s) of %s is available!\nPlease run '%s update' to update to the latest version.\n\n", latestManifest.Version, common.AppName, common.AppName) + } else { + slog.Info("No updates available") + } + } + return nil +} + +func terminateApplication(cmd *cobra.Command, args []string) error { + appContext := cmd.Context().Value(common.AppContext{}).(common.AppContext) + + // clean up temp directory + if appContext.TempDir != "" { + err := os.RemoveAll(appContext.TempDir) + if err != nil { + slog.Error("error cleaning up temp directory", slog.String("tempDir", appContext.TempDir), slog.String("error", err.Error())) + } + } + + slog.Info("Shutting down", slog.String("app", common.AppName), slog.String("version", gVersion), slog.Int("PID", os.Getpid()), slog.String("arguments", strings.Join(os.Args, " "))) + if gLogFile != nil { + gLogFile.Close() + } + return nil +} + +// onIntelNetwork checks if the host is on the Intel network +func onIntelNetwork() bool { + // If we can't lookup the Intel autoproxy domain then we aren't on the Intel + // network + _, err := net.LookupHost("wpad.intel.com") + return err == nil +} + +func checkForUpdates(version string) (bool, manifest, error) { + latestManifest, err := getLatestManifest() + if err != nil { + return false, latestManifest, err + } + slog.Debug("Latest version", slog.String("version", latestManifest.Version)) + slog.Debug("Current version", slog.String("version", version)) + result, err := util.CompareVersions(latestManifest.Version, version) + if err != nil { + return false, latestManifest, err + } + return result == 1, latestManifest, nil +} + +var updateCmd = &cobra.Command{ + GroupID: "other", + Use: "update", + Short: "Update the application", + RunE: func(cmd *cobra.Command, args []string) error { + appContext := cmd.Context().Value(common.AppContext{}).(common.AppContext) + localTempDir := appContext.TempDir + updateAvailable, latestManifest, err := checkForUpdates(gVersion) + if err != nil { + slog.Error("Failed to check for updates", slog.String("error", err.Error())) + fmt.Printf("Error: update check failed: %v\n", err) + return err + } else if updateAvailable { + fmt.Printf("Updating %s to version %s...\n", common.AppName, latestManifest.Version) + err = updateApp(latestManifest, localTempDir) + if err != nil { + slog.Error("Failed to update application", slog.String("error", err.Error())) + fmt.Printf("Error: failed to update application: %v\n", err) + return err + } + } else { + slog.Info("No updates available") + fmt.Printf("No updates available for %s.\n", common.AppName) + } + return nil + }, +} + +func updateApp(latestManifest manifest, localTempDir string) error { + runningAppArgs := os.Args + runningAppPath := runningAppArgs[0] + runningAppDir := filepath.Dir(runningAppPath) + runningAppFile := filepath.Base(runningAppPath) + + // download the latest release + slog.Debug("Downloading latest release") + fileName := "perfspect" + "_" + latestManifest.Version + ".tgz" + url := "https://af01p-fm.devtools.intel.com/artifactory/perfspectnext-fm-local/releases/latest/" + fileName + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + // write the tarball to a temp file + tarballFile, err := os.CreateTemp(localTempDir, "perfspect*.tgz") + if err != nil { + return err + } + defer tarballFile.Close() + slog.Debug("Writing tarball to temp file", slog.String("tempFile", tarballFile.Name())) + _, err = io.Copy(tarballFile, resp.Body) + if err != nil { + return err + } + tarballFile.Close() + // rename the running app to ".sav" + slog.Debug("Renaming running app") + oldAppPath := filepath.Join(runningAppDir, runningAppFile+".sav") + err = os.Rename(runningAppPath, oldAppPath) + if err != nil { + return err + } + // rename the targets.yaml file to ".sav" if it exists + targetsFile := filepath.Join(runningAppDir, "targets.yaml") + if util.Exists(targetsFile) { + slog.Debug("Renaming targets file") + err = os.Rename(targetsFile, targetsFile+".sav") + if err != nil { + return err + } + } + // extract the tarball over the running app's directory + slog.Debug("Extracting tarball") + err = util.ExtractTGZ(tarballFile.Name(), runningAppDir, true) + if err != nil { + slog.Error("Error extracting downloaded tarball", slog.String("error", err.Error())) + slog.Info("Attempting to restore old executable") + errRestore := os.Rename(oldAppPath, runningAppPath) + if errRestore != nil { + slog.Error("Failed to restore old executable", slog.String("error", errRestore.Error())) + } else { + slog.Info("Old executable restored") + } + slog.Info("Attempting to restore targets file") + if util.Exists(targetsFile + ".sav") { + errRestore = os.Rename(targetsFile+".sav", targetsFile) + if errRestore != nil { + slog.Error("Failed to restore targets file", slog.String("error", errRestore.Error())) + } else { + slog.Info("Targets file restored") + } + } + return err + } + // remove the downloaded tarball + slog.Debug("Removing tarball") + err = os.Remove(tarballFile.Name()) + if err != nil { + return err + } + // replace the new targets.yaml with the saved one + if util.Exists(targetsFile + ".sav") { + slog.Debug("Restoring targets file") + err = os.Rename(targetsFile+".sav", targetsFile) + if err != nil { + return err + } + } + fmt.Println("Update completed.") + return nil +} + +type manifest struct { + Version string `json:"version"` + Date string `json:"date"` + Time string `json:"time"` + Commit string `json:"commit"` +} + +func getLatestManifest() (manifest, error) { + // download manifest file from server + url := "https://af01p-fm.devtools.intel.com/artifactory/perfspectnext-fm-local/releases/latest/manifest.json" + resp, err := http.Get(url) + if err != nil { + return manifest{}, err + } + defer resp.Body.Close() + buf := new(bytes.Buffer) + _, err = io.Copy(buf, resp.Body) + if err != nil { + return manifest{}, err + } + // parse json content in buf + var latestManifest manifest + err = json.Unmarshal(buf.Bytes(), &latestManifest) + if err != nil { + return manifest{}, err + } + // return latest version + return latestManifest, nil +} diff --git a/cmd/telemetry/telemetry.go b/cmd/telemetry/telemetry.go new file mode 100644 index 0000000..c1b3003 --- /dev/null +++ b/cmd/telemetry/telemetry.go @@ -0,0 +1,365 @@ +// Package telemetry is a subcommand of the root command. It collects system telemetry from target(s). +package telemetry + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "log/slog" + "os" + "strconv" + "strings" + + "perfspect/internal/common" + "perfspect/internal/report" + "perfspect/internal/script" + "perfspect/internal/util" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "golang.org/x/text/language" + "golang.org/x/text/message" +) + +const cmdName = "telemetry" + +var examples = []string{ + fmt.Sprintf(" Telemetry from local host: $ %s %s", common.AppName, cmdName), + fmt.Sprintf(" Telemetry from remote target: $ %s %s --target 192.168.1.1 --user fred --key fred_key", common.AppName, cmdName), + fmt.Sprintf(" Memory telemetry for 60 seconds: $ %s %s --memory --duration 60", common.AppName, cmdName), + fmt.Sprintf(" Telemetry from multiple targets: $ %s %s --targets targets.yaml", common.AppName, cmdName), +} + +var Cmd = &cobra.Command{ + Use: cmdName, + Aliases: []string{"telem"}, + Short: "Collect system telemetry from target(s)", + Long: "", + Example: strings.Join(examples, "\n"), + RunE: runCmd, + PreRunE: validateFlags, + GroupID: "primary", + Args: cobra.NoArgs, + SilenceErrors: true, +} + +var ( + flagDuration int + flagInterval int + + flagAll bool + + flagCpu bool + flagCpuAvg bool + flagIrq bool + flagNetwork bool + flagStorage bool + flagMemory bool + flagPower bool +) + +const ( + flagDurationName = "duration" + flagIntervalName = "interval" + + flagAllName = "all" + + flagCpuName = "cpu" + flagCpuAvgName = "cpuavg" + flagIrqName = "irq" + flagNetworkName = "network" + flagStorageName = "storage" + flagMemoryName = "memory" + flagPowerName = "power" +) + +var telemetrySummaryTableName = "Telemetry Summary" + +var categories = []common.Category{ + {FlagName: flagCpuName, FlagVar: &flagCpu, DefaultValue: false, Help: "monitor cpu", TableNames: []string{report.CPUUtilizationTableName}}, + {FlagName: flagCpuAvgName, FlagVar: &flagCpuAvg, DefaultValue: false, Help: "monitor cpu average", TableNames: []string{report.AverageCPUUtilizationTableName}}, + {FlagName: flagIrqName, FlagVar: &flagIrq, DefaultValue: false, Help: "monitor irq", TableNames: []string{report.IRQRateTableName}}, + {FlagName: flagStorageName, FlagVar: &flagStorage, DefaultValue: false, Help: "monitor storage", TableNames: []string{report.DriveStatsTableName}}, + {FlagName: flagNetworkName, FlagVar: &flagNetwork, DefaultValue: false, Help: "monitor network", TableNames: []string{report.NetworkStatsTableName}}, + {FlagName: flagMemoryName, FlagVar: &flagMemory, DefaultValue: false, Help: "monitor memory", TableNames: []string{report.MemoryStatsTableName}}, + {FlagName: flagPowerName, FlagVar: &flagPower, DefaultValue: false, Help: "monitor power", TableNames: []string{report.PowerStatsTableName}}, +} + +func init() { + // set up config category flags + for _, cat := range categories { + Cmd.Flags().BoolVar(cat.FlagVar, cat.FlagName, cat.DefaultValue, cat.Help) + } + Cmd.Flags().StringVar(&common.FlagInput, common.FlagInputName, "", "") + Cmd.Flags().BoolVar(&flagAll, flagAllName, false, "") + Cmd.Flags().StringSliceVar(&common.FlagFormat, common.FlagFormatName, []string{report.FormatAll}, "") + Cmd.Flags().IntVar(&flagDuration, flagDurationName, 30, "") + Cmd.Flags().IntVar(&flagInterval, flagIntervalName, 2, "") + + common.AddTargetFlags(Cmd) + + Cmd.SetUsageFunc(usageFunc) +} + +func usageFunc(cmd *cobra.Command) error { + cmd.Printf("Usage: %s [flags]\n\n", cmd.CommandPath()) + cmd.Printf("Examples:\n%s\n\n", cmd.Example) + cmd.Println("Flags:") + for _, group := range getFlagGroups() { + cmd.Printf(" %s:\n", group.GroupName) + for _, flag := range group.Flags { + flagDefault := "" + if cmd.Flags().Lookup(flag.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(flag.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", flag.Name, flag.Help, flagDefault) + } + } + cmd.Println("\nGlobal Flags:") + cmd.Parent().PersistentFlags().VisitAll(func(pf *pflag.Flag) { + flagDefault := "" + if cmd.Parent().PersistentFlags().Lookup(pf.Name).DefValue != "" { + flagDefault = fmt.Sprintf(" (default: %s)", cmd.Flags().Lookup(pf.Name).DefValue) + } + cmd.Printf(" --%-20s %s%s\n", pf.Name, pf.Usage, flagDefault) + }) + return nil +} + +func getFlagGroups() []common.FlagGroup { + var groups []common.FlagGroup + flags := []common.Flag{ + { + Name: flagAllName, + Help: "collect telemetry for all categories", + }, + } + for _, cat := range categories { + flags = append(flags, common.Flag{ + Name: fmt.Sprintf(cat.FlagName), + Help: cat.Help, + }) + } + groups = append(groups, common.FlagGroup{ + GroupName: "Categories", + Flags: flags, + }) + flags = []common.Flag{ + { + Name: common.FlagFormatName, + Help: fmt.Sprintf("choose output format(s) from: %s", strings.Join(append([]string{report.FormatAll}, report.FormatOptions...), ", ")), + }, + { + Name: flagDurationName, + Help: "number of seconds to run the collection. If 0, the collection will run indefinitely. Ctrl-C to stop.", + }, + { + Name: flagIntervalName, + Help: "number of seconds between each sample", + }, + } + groups = append(groups, common.FlagGroup{ + GroupName: "Others Options", + Flags: flags, + }) + groups = append(groups, common.GetTargetFlagGroup()) + flags = []common.Flag{ + { + Name: common.FlagInputName, + Help: "\".raw\" file, or directory containing \".raw\" files. Will skip data collection and use raw data for reports.", + }, + } + groups = append(groups, common.FlagGroup{ + GroupName: "Advanced Options", + Flags: flags, + }) + return groups +} + +func validateFlags(cmd *cobra.Command, args []string) error { + // set flagAll if all categories are selected or if none are selected + if !flagAll { + numCategoriesTrue := 0 + for _, cat := range categories { + if *cat.FlagVar { + numCategoriesTrue++ + break + } + } + if numCategoriesTrue == len(categories) || numCategoriesTrue == 0 { + flagAll = true + } + } + // validate format options + for _, format := range common.FlagFormat { + formatOptions := []string{report.FormatAll} + formatOptions = append(formatOptions, report.FormatOptions...) + if !util.StringInList(format, formatOptions) { + err := fmt.Errorf("format options are: %s", strings.Join(formatOptions, ", ")) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + } + if flagDuration <= 0 { + err := fmt.Errorf("duration must be greater than 0") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + return err + } + return nil +} + +func runCmd(cmd *cobra.Command, args []string) error { + var tableNames []string + for _, cat := range categories { + if *cat.FlagVar || flagAll { + tableNames = append(tableNames, cat.TableNames...) + } + } + // include telemetry summary table if all telemetry options are selected + var summaryFunc common.SummaryFunc + if flagAll { + summaryFunc = summaryFromTableValues + } + // include insights table if all categories are selected + var insightsFunc common.InsightsFunc + if flagAll { + insightsFunc = common.DefaultInsightsFunc + } + reportingCommand := common.ReportingCommand{ + Cmd: cmd, + ReportNamePost: "telem", + Interval: flagInterval, + Duration: flagDuration, + TableNames: tableNames, + SummaryFunc: summaryFunc, + SummaryTableName: telemetrySummaryTableName, + InsightsFunc: insightsFunc, + } + return reportingCommand.Run() +} + +func getTableValues(allTableValues []report.TableValues, tableName string) report.TableValues { + for _, tv := range allTableValues { + if tv.Name == tableName { + return tv + } + } + return report.TableValues{} +} + +func summaryFromTableValues(allTableValues []report.TableValues, _ map[string]script.ScriptOutput) report.TableValues { + cpuUtil := getCPUAveragePercentage(getTableValues(allTableValues, report.AverageCPUUtilizationTableName), "%idle", true) + pkgPower := getMetricAverage(getTableValues(allTableValues, report.PowerStatsTableName), []string{"Package"}, "") + driveReads := getMetricAverage(getTableValues(allTableValues, report.DriveStatsTableName), []string{"kB_read/s"}, "Device") + driveWrites := getMetricAverage(getTableValues(allTableValues, report.DriveStatsTableName), []string{"kB_wrtn/s"}, "Device") + networkReads := getMetricAverage(getTableValues(allTableValues, report.NetworkStatsTableName), []string{"rxkB/s"}, "Time") + networkWrites := getMetricAverage(getTableValues(allTableValues, report.NetworkStatsTableName), []string{"txkB/s"}, "Time") + memAvail := getMetricAverage(getTableValues(allTableValues, report.MemoryStatsTableName), []string{"avail"}, "Time") + return report.TableValues{ + TableDefinition: report.TableDefinition{ + Name: telemetrySummaryTableName, + HasRows: false, + MenuLabel: telemetrySummaryTableName, + }, + Fields: []report.Field{ + {Name: "CPU Utilization (%)", Values: []string{cpuUtil}}, + {Name: "Package Power (Watts)", Values: []string{pkgPower}}, + {Name: "Drive Reads (kB/s)", Values: []string{driveReads}}, + {Name: "Drive Writes (kB/s)", Values: []string{driveWrites}}, + {Name: "Network RX (kB/s)", Values: []string{networkReads}}, + {Name: "Network TX (kB/s)", Values: []string{networkWrites}}, + {Name: "Memory Available (kB)", Values: []string{memAvail}}, + }, + } +} + +func getMetricAverage(tableValues report.TableValues, fieldNames []string, separatorFieldName string) (average string) { + sum, seps, err := getSumOfFields(tableValues.Fields, fieldNames, separatorFieldName) + if err != nil { + slog.Error("failed to get sum of fields for IO metrics", slog.String("error", err.Error())) + return + } + if len(fieldNames) > 0 && seps > 0 { + averageFloat := sum / float64(seps/len(fieldNames)) + p := message.NewPrinter(language.English) // use printer to get commas at thousands, e.g., Memory Available (kB) 258,691,376.80 + average = p.Sprintf("%0.2f", averageFloat) + } + return +} + +func getFieldIndex(fields []report.Field, fieldName string) (int, error) { + for i, field := range fields { + if field.Name == fieldName { + return i, nil + } + } + return -1, fmt.Errorf("field not found: %s", fieldName) +} + +func getSumOfFields(fields []report.Field, fieldNames []string, separatorFieldName string) (sum float64, numSeparators int, err error) { + prevSeparator := "" + var separatorIdx int + if separatorFieldName != "" { + separatorIdx, err = getFieldIndex(fields, separatorFieldName) + if err != nil { + return + } + } + for _, fieldName := range fieldNames { + var fieldIdx int + fieldIdx, err = getFieldIndex(fields, fieldName) + if err != nil { + return + } + for i := range fields[fieldIdx].Values { + valueStr := fields[fieldIdx].Values[i] + var valueFloat float64 + valueFloat, err = strconv.ParseFloat(valueStr, 64) + if err != nil { + return + } + if separatorFieldName != "" { + separator := fields[separatorIdx].Values[i] + if separator != prevSeparator { + numSeparators++ + prevSeparator = separator + } + } else { + numSeparators++ + } + sum += valueFloat + } + } + return +} + +func getCPUAveragePercentage(tableValues report.TableValues, fieldName string, inverse bool) string { + if len(tableValues.Fields) == 0 { + return "" + } + var fieldIndex int + var fv report.Field + for fieldIndex, fv = range tableValues.Fields { + if fv.Name == fieldName { + break + } + } + sum := 0.0 + for _, value := range tableValues.Fields[fieldIndex].Values { + valueFloat, err := strconv.ParseFloat(value, 64) + if err != nil { + slog.Warn("failed to parse float value", slog.String("value", value), slog.String("error", err.Error())) + return "" + } + sum += valueFloat + } + if sum != 0 { + averageFloat := sum / float64(len(tableValues.Fields[fieldIndex].Values)) + if inverse { + averageFloat = 100.0 - averageFloat + } + return fmt.Sprintf("%0.2f", averageFloat) + } + return "" +} diff --git a/docs/daemonset.yml b/docs/daemonset.yml deleted file mode 100644 index adfa3d8..0000000 --- a/docs/daemonset.yml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: intel ---- -apiVersion: apps/v1 -kind: DaemonSet -metadata: - labels: - app: perfspect - name: perfspect - namespace: intel -spec: - selector: - matchLabels: - app: perfspect - template: - metadata: - labels: - app: perfspect - spec: - containers: - - image: - name: perfspect - securityContext: - privileged: true - hostPID: true - restartPolicy: Always \ No newline at end of file diff --git a/events/gnr.txt b/events/gnr.txt deleted file mode 100644 index e485653..0000000 --- a/events/gnr.txt +++ /dev/null @@ -1,96 +0,0 @@ -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -# GraniteRapids event list - -cpu/event=0xd0,umask=0x21,cmask=0x00,name='MEM_INST_RETIRED.LOCK_LOADS'/, -cpu/event=0x51,umask=0x01,cmask=0x00,name='L1D.REPLACEMENT'/, -cpu/event=0xd1,umask=0x01,cmask=0x00,name='MEM_LOAD_RETIRED.L1_HIT'/, -cpu/event=0x24,umask=0xe4,cmask=0x00,name='L2_RQSTS.ALL_CODE_RD'/, -cpu-cycles, -ref-cycles, -instructions; - -cpu/event=0x79,umask=0x08,cmask=0x00,name='IDQ.DSB_UOPS'/, -cpu/event=0x79,umask=0x04,cmask=0x00,name='IDQ.MITE_UOPS'/, -cpu/event=0x79,umask=0x20,cmask=0x00,name='IDQ.MS_UOPS'/, -cpu/event=0xa8,umask=0x01,cmask=0x00,name='LSD.UOPS'/, -cpu-cycles, -ref-cycles, -instructions; - -cpu/event=0x11,umask=0x0e,cmask=0x00,name='ITLB_MISSES.WALK_COMPLETED'/, -cpu/event=0x12,umask=0x0e,cmask=0x00,name='DTLB_LOAD_MISSES.WALK_COMPLETED'/, -cpu/event=0x13,umask=0x0e,cmask=0x00,name='DTLB_STORE_MISSES.WALK_COMPLETED'/, -cpu/event=0x3c,umask=0x08,cmask=0x00,name='CPU_CLK_UNHALTED.REF_DISTRIBUTED'/, -cpu/event=0x3c,umask=0x02,cmask=0x00,name='CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE'/, -cpu-cycles, -ref-cycles, -instructions; - -cpu/event=0xd1,umask=0x02,cmask=0x00,name='MEM_LOAD_RETIRED.L2_HIT'/, -cpu/event=0x25,umask=0x1f,cmask=0x00,name='L2_LINES_IN.ALL'/, -cpu/event=0xd1,umask=0x10,cmask=0x00,name='MEM_LOAD_RETIRED.L2_MISS'/, -cpu/event=0x24,umask=0x24,cmask=0x00,name='L2_RQSTS.CODE_RD_MISS'/, -cpu/event=0xad,umask=0x10,cmask=0x00,name='INT_MISC.UOP_DROPPING'/, -cpu-cycles, -ref-cycles, -instructions; - -cpu/event=0x00,umask=0x04,period=10000003,name='TOPDOWN.SLOTS'/, -cpu/event=0x00,umask=0x81,period=10000003,name='PERF_METRICS.BAD_SPECULATION'/, -cpu/event=0x00,umask=0x83,period=10000003,name='PERF_METRICS.BACKEND_BOUND'/, -cpu/event=0x00,umask=0x82,period=10000003,name='PERF_METRICS.FRONTEND_BOUND'/, -cpu/event=0x00,umask=0x80,period=10000003,name='PERF_METRICS.RETIRING'/, -cpu/event=0x00,umask=0x86,period=10000003,name='PERF_METRICS.FETCH_LATENCY'/, -cpu/event=0x00,umask=0x87,period=10000003,name='PERF_METRICS.MEMORY_BOUND'/, -cpu/event=0x00,umask=0x85,period=10000003,name='PERF_METRICS.BRANCH_MISPREDICTS'/, -cpu/event=0x00,umask=0x84,period=10000003,name='PERF_METRICS.HEAVY_OPERATIONS'/, -cpu-cycles, -ref-cycles, -instructions; - -# kernel -cpu-cycles:k, -ref-cycles:k, -instructions:k; - -# C6 -cstate_core/c6-residency/; -cstate_pkg/c6-residency/; - -# UPI -upi/event=0x02,umask=0x0f,name='UNC_UPI_TxL_FLITS.ALL_DATA'/; - -# CHA (Cache) -cha/event=0x35,umask=0xc80ffe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_CRD'/, -cha/event=0x35,umask=0xc8177e01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE'/, -cha/event=0x36,umask=0xc8177e01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE'/; - -cha/event=0x35,umask=0xC816FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL'/, -cha/event=0x36,umask=0xc816fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL'/, -cha/event=0x35,umask=0xC896FE01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL'/, -cha/event=0x35,umask=0xC8977E01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE'/; - -cha/event=0x35,umask=0xccd7fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA'/, -cha/event=0x35,umask=0xc817fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD'/, -cha/event=0x35,umask=0xc897fe01,name='UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF'/, -cha/event=0x36,umask=0xC817fe01,name='UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD'/; - -# CHA (IO Bandwidth) -cha/event=0x35,umask=0xc8f3ff04,name='UNC_CHA_TOR_INSERTS.IO_PCIRDCUR'/, -cha/event=0x35,umask=0xCC43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOM'/, -cha/event=0x35,umask=0xCD43FF04,name='UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR'/, -cha/event=0x01,umask=0x00,name='UNC_CHA_CLOCKTICKS'/; - -# IMC (memory read/writes) -imc/event=0x05,umask=0xCF,name='UNC_M_CAS_COUNT_SCH0.RD'/, -imc/event=0x05,umask=0xF0,name='UNC_M_CAS_COUNT_SCH0.WR'/, -imc/event=0x06,umask=0xCF,name='UNC_M_CAS_COUNT_SCH1.RD'/, -imc/event=0x06,umask=0xF0,name='UNC_M_CAS_COUNT_SCH1.WR'/; - -# power -power/energy-pkg/, -power/energy-ram/; diff --git a/events/metric_gnr.json b/events/metric_gnr.json deleted file mode 100644 index 512493a..0000000 --- a/events/metric_gnr.json +++ /dev/null @@ -1,186 +0,0 @@ -[ - { - "name": "metric_CPU operating frequency (in GHz)", - "expression": "([cpu-cycles] / [ref-cycles] * [SYSTEM_TSC_FREQ]) / 1000000000" - }, - { - "name": "metric_CPU utilization %", - "expression": "100 * [ref-cycles] / [TSC]" - }, - { - "name": "metric_CPU utilization% in kernel mode", - "expression": "100 * [ref-cycles:k] / [TSC]" - }, - { - "name": "metric_CPI", - "name-txn": "metric_cycles per txn", - "expression": "[cpu-cycles] / [instructions]", - "expression-txn": "[cpu-cycles] / [TXN]" - }, - { - "name": "metric_kernel_CPI", - "name-txn": "metric_kernel_cycles per txn", - "expression": "[cpu-cycles:k] / [instructions:k]", - "expression-txn": "[cpu-cycles:k] / [TXN]" - }, - { - "name": "metric_IPC", - "name-txn": "metric_txn per cycle", - "expression": "[instructions] / [cpu-cycles]", - "expression-txn": "[TXN] / [cpu-cycles]" - }, - { - "name": "metric_giga_instructions_per_sec", - "expression": "[instructions] / 1000000000" - }, - { - "name": "metric_locks retired per instr", - "name-txn": "metric_locks retired per txn", - "expression": "[MEM_INST_RETIRED.LOCK_LOADS] / [instructions]", - "expression-txn": "[MEM_INST_RETIRED.LOCK_LOADS] / [TXN]" - }, - { - "name": "metric_L1D MPI (includes data+rfo w/ prefetches)", - "name-txn": "metric_L1D misses per txn (includes data+rfo w/ prefetches)", - "expression": "[L1D.REPLACEMENT] / [instructions]", - "expression-txn": "[L1D.REPLACEMENT] / [TXN]" - }, - { - "name": "metric_L1D demand data read hits per instr", - "name-txn": "metric_L1D demand data read hits per txn", - "expression": "[MEM_LOAD_RETIRED.L1_HIT] / [instructions]", - "expression-txn": "[MEM_LOAD_RETIRED.L1_HIT] / [TXN]" - }, - { - "name": "metric_L1-I code read misses (w/ prefetches) per instr", - "name-txn": "metric_L1I code read misses (includes prefetches) per txn", - "expression": "[L2_RQSTS.ALL_CODE_RD] / [instructions]", - "expression-txn": "[L2_RQSTS.ALL_CODE_RD] / [TXN]" - }, - { - "name": "metric_L2 demand data read hits per instr", - "name-txn": "metric_L2 demand data read hits per txn", - "expression": "[MEM_LOAD_RETIRED.L2_HIT] / [instructions]", - "expression-txn": "[MEM_LOAD_RETIRED.L2_HIT] / [TXN]" - }, - { - "name": "metric_L2 MPI (includes code+data+rfo w/ prefetches)", - "name-txn": "metric_L2 misses per txn (includes code+data+rfo w/ prefetches)", - "expression": "[L2_LINES_IN.ALL] / [instructions]", - "expression-txn": "[L2_LINES_IN.ALL] / [TXN]" - }, - { - "name": "metric_L2 demand data read MPI", - "name-txn": "metric_L2 demand data read misses per txn", - "expression": "[MEM_LOAD_RETIRED.L2_MISS] / [instructions]", - "expression-txn": "[MEM_LOAD_RETIRED.L2_MISS] / [TXN]" - }, - { - "name": "metric_L2 demand code MPI", - "name-txn": "metric_L2 demand code misses per txn", - "expression": "[L2_RQSTS.CODE_RD_MISS] / [instructions]", - "expression-txn": "[L2_RQSTS.CODE_RD_MISS] / [TXN]" - }, - { - "name": "metric_LLC code read MPI (demand+prefetch)", - "name-txn": "metric_LLC code read (demand+prefetch) misses per txn", - "expression": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [instructions]", - "expression-txn": "[UNC_CHA_TOR_INSERTS.IA_MISS_CRD] / [TXN]" - }, - { - "name": "metric_LLC data read MPI (demand+prefetch)", - "name-txn": "metric_LLC data read (demand+prefetch) misses per txn", - "expression": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [instructions]", - "expression-txn": "([UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF]) / [TXN]" - }, - { - "name": "metric_Average LLC demand data read miss latency (in ns)", - "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" - }, - { - "name": "metric_Average LLC demand data read miss latency for LOCAL requests (in ns)", - "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" - }, - { - "name": "metric_Average LLC demand data read miss latency for REMOTE requests (in ns)", - "expression": "( 1000000000 * ([UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE] / [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE]) / ([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) ) ) * 1" - }, - { - "name": "metric_UPI Data transmit BW (MB/sec) (only data)", - "expression": "([UNC_UPI_TxL_FLITS.ALL_DATA] * (64 / 9.0) / 1000000) / 1" - }, - { - "name": "metric_package power (watts)", - "expression": "[power/energy-pkg/]" - }, - { - "name": "metric_DRAM power (watts)", - "expression": "[power/energy-ram/]" - }, - { - "name": "metric_core c6 residency %", - "expression": "100 * [cstate_core/c6-residency/] / [TSC]" - }, - { - "name": "metric_package c6 residency %", - "expression": "100 * [cstate_pkg/c6-residency/] * [CORES_PER_SOCKET] / [TSC]" - }, - { - "name": "metric_% Uops delivered from decoded Icache (DSB)", - "expression": "100 * ([IDQ.DSB_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" - }, - { - "name": "metric_% Uops delivered from legacy decode pipeline (MITE)", - "expression": "100 * ([IDQ.MITE_UOPS] / ([IDQ.DSB_UOPS] + [IDQ.MITE_UOPS] + [IDQ.MS_UOPS] + [LSD.UOPS]) )" - }, - { - "name": "metric_memory bandwidth read (MB/sec)", - "expression": "(([UNC_M_CAS_COUNT_SCH0.RD] + [UNC_M_CAS_COUNT_SCH1.RD]) * 64 / 1000000) / 1" - }, - { - "name": "metric_memory bandwidth write (MB/sec)", - "expression": "(([UNC_M_CAS_COUNT_SCH0.WR] + [UNC_M_CAS_COUNT_SCH1.WR]) * 64 / 1000000) / 1" - }, - { - "name": "metric_memory bandwidth total (MB/sec)", - "expression": "(([UNC_M_CAS_COUNT_SCH0.RD] + [UNC_M_CAS_COUNT_SCH1.RD] + [UNC_M_CAS_COUNT_SCH0.WR] + [UNC_M_CAS_COUNT_SCH1.WR]) * 64 / 1000000) / 1" - }, - { - "name": "metric_ITLB (2nd level) MPI", - "name-txn": "metric_ITLB (2nd level) misses per txn", - "expression": "[ITLB_MISSES.WALK_COMPLETED] / [instructions]", - "expression-txn": "[ITLB_MISSES.WALK_COMPLETED] / [TXN]" - }, - { - "name": "metric_DTLB (2nd level) load MPI", - "name-txn": "metric_DTLB (2nd level) load misses per txn", - "expression": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [instructions]", - "expression-txn": "[DTLB_LOAD_MISSES.WALK_COMPLETED] / [TXN]" - }, - { - "name": "metric_DTLB (2nd level) store MPI", - "name-txn": "metric_DTLB (2nd level) store misses per txn", - "expression": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [instructions]", - "expression-txn": "[DTLB_STORE_MISSES.WALK_COMPLETED] / [TXN]" - }, - { - "name": "metric_NUMA %_Reads addressed to local DRAM", - "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" - }, - { - "name": "metric_NUMA %_Reads addressed to remote DRAM", - "expression": "100 * ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE]) / ([UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE] + [UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE])" - }, - { - "name": "metric_uncore frequency GHz", - "expression": "([UNC_CHA_CLOCKTICKS] / ([CHAS_PER_SOCKET] * [SOCKET_COUNT]) / 1000000000) / 1" - }, - { - "name": "metric_IO_bandwidth_disk_or_network_writes (MB/sec)", - "expression": "([UNC_CHA_TOR_INSERTS.IO_PCIRDCUR] * 64 / 1000000) / 1" - }, - { - "name": "metric_IO_bandwidth_disk_or_network_reads (MB/sec)", - "expression": "(([UNC_CHA_TOR_INSERTS.IO_ITOM] + [UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR]) * 64 / 1000000) / 1" - } -] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ca52ae0 --- /dev/null +++ b/go.mod @@ -0,0 +1,37 @@ +module perfspect + +go 1.23.0 + +replace ( + perfspect/internal/common => ./internal/common + perfspect/internal/cpudb => ./internal/cpudb + perfspect/internal/progress => ./internal/progress + perfspect/internal/report => ./internal/report + perfspect/internal/script => ./internal/script + perfspect/internal/target => ./internal/target + perfspect/internal/util => ./internal/util +) + +require ( + github.com/Knetic/govaluate v3.0.0+incompatible + github.com/deckarep/golang-set/v2 v2.6.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/pflag v1.0.5 + github.com/xuri/excelize/v2 v2.9.0 + golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 + golang.org/x/term v0.25.0 + golang.org/x/text v0.19.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.4 // indirect + github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect + github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8f653b1 --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= +github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY= +github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE= +github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE= +github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A= +github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hotspot.py b/hotspot.py deleted file mode 100644 index f741cb3..0000000 --- a/hotspot.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -import logging -import os -import platform -import shlex -import shutil -import subprocess -import sys - -from argparse import ArgumentParser -from src.common import configure_logging, crash -from src.perf_helpers import get_perf_list - - -def fix_path(script): - return os.path.join( - getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))), - script, - ) - - -def attach_perf_map_agent(): - # look for java processes - try: - pids = ( - subprocess.check_output(shlex.split("pgrep java"), encoding="UTF-8") - .strip() - .split("\n") - ) - except subprocess.CalledProcessError: - return - - if len(pids) > 0 and pids[0] != "": - logging.info("detected java processes: " + str(pids)) - - # setup tmp folder for storing perf-map-agent - if not os.path.exists("/tmp/perfspect"): - os.mkdir("/tmp/perfspect") - shutil.copy(fix_path("attach-main.jar"), "/tmp/perfspect") - shutil.copy(fix_path("libperfmap.so"), "/tmp/perfspect") - os.chmod("/tmp/perfspect/attach-main.jar", 0o666) - os.chmod("/tmp/perfspect/libperfmap.so", 0o666) - - for pid in pids: - uid = subprocess.check_output( - shlex.split("awk '/^Uid:/{print $2}' /proc/" + pid + "/status"), - encoding="UTF-8", - ) - gid = subprocess.check_output( - shlex.split("awk '/^Gid:/{print $2}' /proc/" + pid + "/status"), - encoding="UTF-8", - ) - JAVA_HOME = subprocess.check_output( - shlex.split('sed "s:bin/java::"'), - input=subprocess.check_output( - shlex.split("readlink -f /usr/bin/java"), encoding="UTF-8" - ), - encoding="UTF-8", - ).strip() - current_dir = os.getcwd() - try: - os.chdir("/tmp/perfspect/") - subprocess.check_call( - shlex.split( - f"sudo -u \\#{uid} -g \\#{gid} {JAVA_HOME}bin/java -cp /tmp/perfspect/attach-main.jar:{JAVA_HOME}lib/tools.jar net.virtualvoid.perf.AttachOnce {pid}" - ), - encoding="UTF-8", # type: ignore - ) - logging.info("Successfully attached perf-map-agent to: " + pid) - except subprocess.CalledProcessError: - logging.info("Failed to attach perf-map-agent to: " + pid) - os.chdir(current_dir) - - -if __name__ == "__main__": - configure_logging(".") - - parser = ArgumentParser( - description="hotspot: PMU based flamegraphs for hotspot analysis" - ) - parser.add_argument( - "-t", - "--timeout", - required=True, - type=int, - help="collection time", - ) - args = parser.parse_args() - if os.geteuid() != 0: - crash("Must run as root, please re-run") - if platform.system() != "Linux": - crash("PerfSpect currently supports Linux only") - get_perf_list() - - events = ["instructions", "cycles", "branch-misses", "cache-misses"] - - logging.info("collecting...") - - attach_perf_map_agent() - - subprocess.run( - shlex.split( - "sudo perf record -a -g -F 99 -e " - + ",".join(events) - + " sleep " - + str(args.timeout) - ) - ) - - logging.info("postprocessing...") - - script = subprocess.run( - shlex.split("perf script"), - stdout=subprocess.PIPE, - ) - cycles_collapse = "" - with open("cycles.col", "w") as c: - cycles_collapse = subprocess.run( - shlex.split(fix_path("stackcollapse-perf.pl") + ' --event-filter="cycles"'), - input=script.stdout, - stdout=c, - ) - for event, subtitle, differential in [ - ["branch-misses", "What is being stalled by poor prefetching", False], - ["cache-misses", "What is being stalled by poor caching", False], - ["instructions", "CPI: blue = vectorized, red = stalled", True], - ]: - with open(event + ".svg", "w") as f: - collapse = "" - with open(event + ".col", "w") as e: - collapse = subprocess.run( - shlex.split( - fix_path("stackcollapse-perf.pl") - + ' --event-filter="' - + event - + '"' - ), - input=script.stdout, - stdout=e, - ) - if differential: - with open("diff.col", "w") as e: - collapse = subprocess.run( - shlex.split( - fix_path("difffolded.pl") + " " + event + ".col cycles.col" - ), - stdout=e, - ) - with open("diff.col" if differential else event + ".col", "r") as e: - flamegraph = subprocess.run( - shlex.split( - fix_path("flamegraph.pl") - + ' --title="' - + event - + '" --subtitle="' - + subtitle - + '"' - ), - stdin=e, - stdout=f, - ) - os.remove(event + ".col") - if differential: - os.remove("diff.col") - os.chmod(event + ".svg", 0o666) - logging.info("generated " + event + ".svg") - - os.remove("cycles.col") diff --git a/internal/common/common.go b/internal/common/common.go new file mode 100644 index 0000000..143f1a0 --- /dev/null +++ b/internal/common/common.go @@ -0,0 +1,403 @@ +// Package common includes functions that are used across the different commands +package common + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "log/slog" + "os" + "os/signal" + "path/filepath" + "perfspect/internal/progress" + "perfspect/internal/report" + "perfspect/internal/script" + "perfspect/internal/target" + "perfspect/internal/util" + "syscall" + + "github.com/spf13/cobra" +) + +var AppName = filepath.Base(os.Args[0]) + +// AppContext represents the application context that can be accessed from all commands. +type AppContext struct { + OutputDir string // OutputDir is the directory where the application will write output files. + TempDir string // TempDir is the local host's temp directory. + Version string // Version is the version of the application. +} + +type Flag struct { + Name string + Help string +} +type FlagGroup struct { + GroupName string + Flags []Flag +} + +type TargetScriptOutputs struct { + targetName string + scriptOutputs map[string]script.ScriptOutput +} + +const ( + TableNameInsights = "Insights" + TableNamePerfspect = "PerfSpect Version" +) + +type Category struct { + FlagName string + TableNames []string + FlagVar *bool + DefaultValue bool + Help string +} + +var ( + FlagInput string + FlagFormat []string +) + +const ( + FlagInputName = "input" + FlagFormatName = "format" +) + +func CreateOutputDir(outputDir string) error { + if err := os.MkdirAll(outputDir, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + return nil +} + +type SummaryFunc func([]report.TableValues, map[string]script.ScriptOutput) report.TableValues +type InsightsFunc SummaryFunc + +type ReportingCommand struct { + Cmd *cobra.Command + ReportNamePost string + TableNames []string + Duration int + Interval int + Frequency int + SummaryFunc SummaryFunc + SummaryTableName string + InsightsFunc InsightsFunc +} + +func (rc *ReportingCommand) Run() error { + // appContext is the application context that holds common data and resources. + appContext := rc.Cmd.Context().Value(AppContext{}).(AppContext) + localTempDir := appContext.TempDir + outputDir := appContext.OutputDir + // handle signals + // child processes will exit when the signals are received which will + // allow this app to exit normally + sigChannel := make(chan os.Signal, 1) + signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-sigChannel + slog.Info("received signal", slog.String("signal", sig.String())) + }() + // get the data we need to generate reports + var orderedTargetScriptOutputs []TargetScriptOutputs + if FlagInput != "" { + // read the raw file(s) as JSON + rawReports, err := report.ReadRawReports(FlagInput) + if err != nil { + err = fmt.Errorf("failed to read raw file(s): %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + rc.TableNames = []string{} // use the table names from the raw files + for _, rawReport := range rawReports { + for _, tableName := range rawReport.TableNames { // just in case someone tries to use the raw files that were collected with a different set of categories + // filter out tables that we add after processing + if tableName == TableNameInsights || tableName == TableNamePerfspect || tableName == rc.SummaryTableName { + continue + } + rc.TableNames = util.UniqueAppend(rc.TableNames, tableName) + } + orderedTargetScriptOutputs = append(orderedTargetScriptOutputs, TargetScriptOutputs{targetName: rawReport.TargetName, scriptOutputs: rawReport.ScriptOutputs}) + } + } else { + // get the list of unique scripts to run and tables we're interested in + var scriptNames []string + for _, tableName := range rc.TableNames { + // add scripts to list of scripts to run + for _, scriptName := range report.GetScriptNamesForTable(tableName) { + scriptNames = util.UniqueAppend(scriptNames, scriptName) + } + } + // make a list of unique script definitions + var scriptsToRun []script.ScriptDefinition + for _, scriptName := range scriptNames { + scriptsToRun = append(scriptsToRun, script.GetTimedScriptByName(scriptName, rc.Duration, rc.Interval, rc.Frequency)) + } + // do any of the scripts require elevated privileges? + elevated := false + for _, script := range scriptsToRun { + if script.Superuser { + elevated = true + break + } + } + // get the targets + myTargets, err := GetTargets(rc.Cmd, elevated, false, localTempDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + if len(myTargets) == 0 { + err := fmt.Errorf("no targets specified") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + // setup and start the progress indicator + multiSpinner := progress.NewMultiSpinner() + for _, target := range myTargets { + err := multiSpinner.AddSpinner(target.GetName()) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + } + multiSpinner.Start() + // run the scripts on the targets + channelTargetScriptOutputs := make(chan TargetScriptOutputs) + channelError := make(chan error) + for _, target := range myTargets { + go collectOnTarget(rc.Cmd, target, scriptsToRun, localTempDir, channelTargetScriptOutputs, channelError, multiSpinner.Status) + } + // wait for scripts to run on all targets + var allTargetScriptOutputs []TargetScriptOutputs + for range myTargets { + select { + case scriptOutputs := <-channelTargetScriptOutputs: + allTargetScriptOutputs = append(allTargetScriptOutputs, scriptOutputs) + case err := <-channelError: + slog.Error(err.Error()) + } + } + // allTargetScriptOutputs is in the order of data collection completion + // reorder to match order of myTargets + for _, target := range myTargets { + for _, targetScriptOutputs := range allTargetScriptOutputs { + if targetScriptOutputs.targetName == target.GetName() { + orderedTargetScriptOutputs = append(orderedTargetScriptOutputs, targetScriptOutputs) + break + } + } + } + multiSpinner.Finish() + fmt.Println() + } + err := CreateOutputDir(outputDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + // create the raw report before processing the data, so that we can save the raw data even if there is an error while processing + for _, targetScriptOutputs := range orderedTargetScriptOutputs { + reportBytes, err := report.CreateRawReport(rc.TableNames, targetScriptOutputs.scriptOutputs, targetScriptOutputs.targetName) + if err != nil { + err = fmt.Errorf("failed to create raw report: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + post := "" + if rc.ReportNamePost != "" { + post = "_" + rc.ReportNamePost + } + reportFilename := fmt.Sprintf("%s%s.%s", targetScriptOutputs.targetName, post, "raw") + reportPath := filepath.Join(appContext.OutputDir, reportFilename) + if err = report.WriteReport(reportBytes, reportPath); err != nil { + err = fmt.Errorf("failed to write report: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + } + // check report formats + formats := FlagFormat + if util.StringInList(report.FormatAll, formats) { + formats = report.FormatOptions + } + // process the collected data and create the requested report(s) + allTargetsTableValues := make([][]report.TableValues, 0) + var reportFilePaths []string + for _, targetScriptOutputs := range orderedTargetScriptOutputs { + scriptOutputs := targetScriptOutputs.scriptOutputs + // process the tables, i.e., get field values from script output + allTableValues, err := report.Process(rc.TableNames, scriptOutputs) + if err != nil { + err = fmt.Errorf("failed to process collected data: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + // special case - the summary table is built from the post-processed data, i.e., table values + if rc.SummaryFunc != nil { + // override the menu label for the System Summary table to avoid conflict with performance summary table added below + for i, tv := range allTableValues { + if tv.Name == report.SystemSummaryTableName { + allTableValues[i].MenuLabel = "System Summary" + } + } + summaryTableValues := rc.SummaryFunc(allTableValues, targetScriptOutputs.scriptOutputs) + allTableValues = append(allTableValues, summaryTableValues) + } + // special case - add tableValues for Insights + if rc.InsightsFunc != nil { + insightsTableValues := rc.InsightsFunc(allTableValues, targetScriptOutputs.scriptOutputs) + allTableValues = append(allTableValues, insightsTableValues) + } + // special case - add tableValues for the application version + allTableValues = append(allTableValues, report.TableValues{ + TableDefinition: report.TableDefinition{ + Name: TableNamePerfspect, + }, + Fields: []report.Field{ + {Name: "Version", Values: []string{appContext.Version}}, + }, + }) + // create the report(s) + for _, format := range formats { + reportBytes, err := report.Create(format, allTableValues, scriptOutputs, targetScriptOutputs.targetName) + if err != nil { + err = fmt.Errorf("failed to create report: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + if len(formats) == 1 && format == report.FormatTxt { + fmt.Printf("%s:\n", targetScriptOutputs.targetName) + fmt.Print(string(reportBytes)) + } + post := "" + if rc.ReportNamePost != "" { + post = "_" + rc.ReportNamePost + } + reportFilename := fmt.Sprintf("%s%s.%s", targetScriptOutputs.targetName, post, format) + reportPath := filepath.Join(appContext.OutputDir, reportFilename) + if err = report.WriteReport(reportBytes, reportPath); err != nil { + err = fmt.Errorf("failed to write report: %w", err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + reportFilePaths = append(reportFilePaths, reportPath) + } + // keep all the targets table values for combined reports + allTargetsTableValues = append(allTargetsTableValues, allTableValues) + } + if len(allTargetsTableValues) > 1 { + // list of target names for the combined report + // - only those that we received output from + targetNames := make([]string, 0) + for _, targetScriptOutputs := range orderedTargetScriptOutputs { + targetNames = append(targetNames, targetScriptOutputs.targetName) + } + multiTargetFormats := []string{report.FormatHtml, report.FormatXlsx} + for _, format := range multiTargetFormats { + if !util.StringInList(format, formats) { + continue + } + reportBytes, err := report.CreateMultiTarget(format, allTargetsTableValues, targetNames) + if err != nil { + err = fmt.Errorf("failed to create multi-target %s report: %w", format, err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + reportFilename := fmt.Sprintf("%s.%s", "all_hosts", format) + reportPath := filepath.Join(appContext.OutputDir, reportFilename) + if err = report.WriteReport(reportBytes, reportPath); err != nil { + err = fmt.Errorf("failed to write multi-target %s report: %w", format, err) + fmt.Fprintf(os.Stderr, "Error: %+v\n", err) + slog.Error(err.Error()) + rc.Cmd.SilenceUsage = true + return err + } + reportFilePaths = append(reportFilePaths, reportPath) + } + } + if len(reportFilePaths) > 0 { + fmt.Println("Report files:") + } + for _, reportFilePath := range reportFilePaths { + fmt.Printf(" %s\n", reportFilePath) + } + return nil + +} + +func DefaultInsightsFunc(allTableValues []report.TableValues, scriptOutputs map[string]script.ScriptOutput) report.TableValues { + insightsTableValues := report.TableValues{ + TableDefinition: report.TableDefinition{ + Name: TableNameInsights, + HasRows: true, + MenuLabel: TableNameInsights, + }, + Fields: []report.Field{ + {Name: "Recommendation", Values: []string{}}, + {Name: "Justification", Values: []string{}}, + }, + } + for _, tableValues := range allTableValues { + for _, insight := range tableValues.Insights { + insightsTableValues.Fields[0].Values = append(insightsTableValues.Fields[0].Values, insight.Recommendation) + insightsTableValues.Fields[1].Values = append(insightsTableValues.Fields[1].Values, insight.Justification) + } + } + return insightsTableValues +} + +func collectOnTarget(cmd *cobra.Command, myTarget target.Target, scriptsToRun []script.ScriptDefinition, localTempDir string, channelTargetScriptOutputs chan TargetScriptOutputs, channelError chan error, statusUpdate progress.MultiSpinnerUpdateFunc) { + // create a temporary directory on the target + var targetTempDir string + var err error + statusUpdate(myTarget.GetName(), "creating temporary directory") + targetTempRoot, _ := cmd.Flags().GetString(FlagTargetTempDirName) + if targetTempDir, err = myTarget.CreateTempDirectory(targetTempRoot); err != nil { + statusUpdate(myTarget.GetName(), fmt.Sprintf("error creating temporary directory: %v", err)) + err = fmt.Errorf("error creating temporary directory on %s: %v", myTarget.GetName(), err) + channelError <- err + return + } + // don't remove the directory if we're debugging + if cmd.Parent().PersistentFlags().Lookup("debug").Value.String() != "true" { + defer myTarget.RemoveDirectory(targetTempDir) + } + // run the scripts on the target + statusUpdate(myTarget.GetName(), "collecting data") + scriptOutputs, err := script.RunScripts(myTarget, scriptsToRun, true, localTempDir) + if err != nil { + statusUpdate(myTarget.GetName(), fmt.Sprintf("error collecting data: %v", err)) + err = fmt.Errorf("error running data collection scripts on %s: %v", myTarget.GetName(), err) + channelError <- err + return + } + statusUpdate(myTarget.GetName(), "collection complete") + channelTargetScriptOutputs <- TargetScriptOutputs{targetName: myTarget.GetName(), scriptOutputs: scriptOutputs} +} diff --git a/internal/common/targets.go b/internal/common/targets.go new file mode 100644 index 0000000..ab2970d --- /dev/null +++ b/internal/common/targets.go @@ -0,0 +1,268 @@ +package common + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "log/slog" + "os" + "os/user" + "path" + "perfspect/internal/script" + "perfspect/internal/target" + "perfspect/internal/util" + "runtime" + + "github.com/spf13/cobra" + "golang.org/x/term" + "gopkg.in/yaml.v2" +) + +// target flags +var ( + flagTargetHost string + flagTargetPort string + flagTargetUser string + flagTargetKeyFile string + flagTargetsFile string + flagTargetTempDir string +) + +// target flag names +const ( + flagTargetsFileName = "targets" + flagTargetHostName = "target" + flagTargetPortName = "port" + flagTargetUserName = "user" + flagTargetKeyName = "key" + FlagTargetTempDirName = "targettemp" +) + +var targetFlags = []Flag{ + {Name: flagTargetHostName, Help: "host name or IP address of remote target"}, + {Name: flagTargetPortName, Help: "port for SSH to remote target"}, + {Name: flagTargetUserName, Help: "user name for SSH to remote target"}, + {Name: flagTargetKeyName, Help: "private key file for SSH to remote target"}, + {Name: flagTargetsFileName, Help: "file with remote target(s) connection details. See targets.yaml for format."}, + {Name: FlagTargetTempDirName, Help: "directory to use on remote target for temporary files"}, +} + +func AddTargetFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&flagTargetHost, flagTargetHostName, "", targetFlags[0].Help) + cmd.Flags().StringVar(&flagTargetPort, flagTargetPortName, "", targetFlags[1].Help) + cmd.Flags().StringVar(&flagTargetUser, flagTargetUserName, "", targetFlags[2].Help) + cmd.Flags().StringVar(&flagTargetKeyFile, flagTargetKeyName, "", targetFlags[3].Help) + cmd.Flags().StringVar(&flagTargetsFile, flagTargetsFileName, "", targetFlags[4].Help) + cmd.Flags().StringVar(&flagTargetTempDir, FlagTargetTempDirName, "", targetFlags[5].Help) + + cmd.MarkFlagsMutuallyExclusive(flagTargetHostName, flagTargetsFileName) +} + +func GetTargetFlagGroup() FlagGroup { + return FlagGroup{ + GroupName: "Remote Target Options", + Flags: targetFlags, + } +} + +// GetTargets retrieves the list of targets based on the provided command and parameters. +// If a targets file is specified, it reads the targets from the file. +// Otherwise, it retrieves a single target using the getTarget function. +// The function returns a slice of target.Target and an error if any. +func GetTargets(cmd *cobra.Command, needsElevatedPrivileges bool, failIfCantElevate bool, localTempDir string) ([]target.Target, error) { + flagTargetsFile, _ := cmd.Flags().GetString(flagTargetsFileName) + if flagTargetsFile != "" { + return getTargetsFromFile(flagTargetsFile, localTempDir) + } + myTarget, err := getTarget(cmd, needsElevatedPrivileges, failIfCantElevate, localTempDir) + if err != nil { + return nil, err + } + return []target.Target{myTarget}, nil +} + +// getTarget returns a target.Target object representing the target host and associated details. +// The function takes the following parameters: +// - cmd: A pointer to the cobra.Command object representing the command. +// - needsElevatedPriviliges: A boolean indicating whether elevated privileges are required. +// - failIfCantElevate: A boolean indicating whether to fail if elevated privileges can't be obtained. +// - localTempDir: A string representing the local temporary directory. +// The function returns the following values: +// - myTarget: A target.Target object representing the target host and associated details. +// - err: An error object indicating any error that occurred during the retrieval process. +func getTarget(cmd *cobra.Command, needsElevatedPrivileges bool, failIfCantElevate bool, localTempDir string) (target.Target, error) { + targetHost, _ := cmd.Flags().GetString(flagTargetHostName) + targetPort, _ := cmd.Flags().GetString(flagTargetPortName) + targetUser, _ := cmd.Flags().GetString(flagTargetUserName) + targetKey, _ := cmd.Flags().GetString(flagTargetKeyName) + if targetHost != "" { + myTarget := target.NewRemoteTarget(targetHost, targetHost, targetPort, targetUser, targetKey) + if !myTarget.CanConnect() { + if targetKey == "" && targetUser != "" { + if !term.IsTerminal(int(os.Stdin.Fd())) { + err := fmt.Errorf("can not prompt for SSH password because STDIN isn't coming from a terminal") + slog.Error(err.Error()) + return myTarget, err + } else { + slog.Info("Prompting for SSH password.", slog.String("targetHost", targetHost), slog.String("targetUser", targetUser)) + sshPwd, err := getPassword(fmt.Sprintf("%s@%s's password", targetUser, targetHost)) + if err != nil { + return nil, err + } + var hostArchitecture string + hostArchitecture, err = getHostArchitecture() + if err != nil { + return nil, err + } + sshPassPath, err := util.ExtractResource(script.Resources, path.Join("resources", hostArchitecture, "sshpass"), localTempDir) + if err != nil { + return nil, err + } + myTarget.SetSshPassPath(sshPassPath) + myTarget.SetSshPass(sshPwd) + // if still can't connect, return error + if !myTarget.CanConnect() { + err = fmt.Errorf("failed to connect to target host (%s) using provided ssh user (%s) and password (****)", targetHost, targetUser) + return nil, err + } + } + } else { + err := fmt.Errorf("failed to connect to target (%s) using provided target arguments", targetHost) + return nil, err + } + } + if needsElevatedPrivileges && !myTarget.CanElevatePrivileges() { + if failIfCantElevate { + err := fmt.Errorf("failed to elevate privileges on remote target") + return nil, err + } else { + slog.Warn("failed to elevate privileges on remote target, continuing without elevated privileges", slog.String("targetHost", targetHost)) + } + } + return myTarget, nil + } + // local target + myTarget := target.NewLocalTarget() + if needsElevatedPrivileges && !myTarget.CanElevatePrivileges() { + if !term.IsTerminal(int(os.Stdin.Fd())) { + slog.Warn("can not prompt for sudo password because STDIN isn't coming from a terminal") + if failIfCantElevate { + err := fmt.Errorf("failed to elevate privileges on local target") + return nil, err + } else { + slog.Warn("continuing without elevated privileges") + } + } else { + fmt.Fprintf(os.Stderr, "WARNING: some operations cannot be performed without elevated privileges.\n") + currentUser, err := user.Current() + if err != nil { + return nil, err + } + fmt.Fprintf(os.Stderr, "For complete functionality, please provide your password at the prompt.\n") + slog.Info("prompting for sudo password") + prompt := fmt.Sprintf("[sudo] password for %s", currentUser.Username) + var sudoPwd string + sudoPwd, err = getPassword(prompt) + if err != nil { + return nil, err + } + myTarget.SetSudo(sudoPwd) + if !myTarget.CanElevatePrivileges() { + if failIfCantElevate { + err := fmt.Errorf("failed to elevate privileges on local target") + return nil, err + } else { + slog.Warn("failed to elevate privileges on local target, continuing without elevated privileges") + fmt.Fprintf(os.Stderr, "WARNING: Not able to establish elevated privileges with provided password.\n") + fmt.Fprintf(os.Stderr, "Continuing with regular user privileges. Some operations will not be performed.\n") + } + } + } + } + return myTarget, nil +} + +type targetFromYAML struct { + Name string `yaml:"name"` + Host string `yaml:"host"` + Port string `yaml:"port"` + User string `yaml:"user"` + Key string `yaml:"key"` + Pwd string `yaml:"pwd"` +} + +type targetsFile struct { + Targets []targetFromYAML `yaml:"targets"` +} + +// getTargetsFromFile reads a targets file and returns a list of target objects. +// It takes the path to the targets file and the local temporary directory as input. +func getTargetsFromFile(targetsFilePath string, localTempDir string) (targets []target.Target, err error) { + var targetsFile targetsFile + // read the file into a byte array + yamlFile, err := os.ReadFile(targetsFilePath) + if err != nil { + return + } + // parse the file contents into a targetsFile struct + err = yaml.Unmarshal(yamlFile, &targetsFile) + if err != nil { + return + } + + // if any of the targets require a password, extract sshpass from resources + needsSshPass := false + for _, t := range targetsFile.Targets { + if t.Pwd != "" { + needsSshPass = true + break + } + } + var sshPassPath string + if needsSshPass { + var hostArchitecture string + hostArchitecture, err = getHostArchitecture() + if err != nil { + return + } + sshPassPath, err = util.ExtractResource(script.Resources, path.Join("resources", hostArchitecture, "sshpass"), localTempDir) + if err != nil { + return + } + } + // create target objects from the targetFromYAML structs + for _, t := range targetsFile.Targets { + newTarget := target.NewRemoteTarget(t.Name, t.Host, t.Port, t.User, t.Key) + newTarget.SetSshPassPath(sshPassPath) + newTarget.SetSshPass(t.Pwd) + targets = append(targets, newTarget) + } + return +} + +// getPassword prompts the user for a password and returns it as a string. +// It takes a prompt string as input and displays the prompt to the user. +// The user's input is hidden as they type, and the entered password is returned as a string. +// If an error occurs while reading the password, it is returned along with an empty string. +func getPassword(prompt string) (string, error) { + fmt.Fprintf(os.Stderr, "\n%s: ", prompt) + pwd, err := term.ReadPassword(0) + if err != nil { + return "", err + } + fmt.Fprintf(os.Stderr, "\n") // newline after password + return string(pwd), nil +} + +func getHostArchitecture() (string, error) { + if runtime.GOARCH == "amd64" { + return "x86_64", nil + } else if runtime.GOARCH == "arm64" { + return "aarch64", nil + } else { + slog.Error("unsupported architecture", slog.String("architecture", runtime.GOARCH)) + err := fmt.Errorf("unsupported architecture: %s", runtime.GOARCH) + return "", err + } +} diff --git a/internal/cpudb/cpu_defs.go b/internal/cpudb/cpu_defs.go new file mode 100644 index 0000000..4ee0b9c --- /dev/null +++ b/internal/cpudb/cpu_defs.go @@ -0,0 +1,60 @@ +package cpudb + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// CPU - used to lookup micro architecture and channels by family, model, and stepping +// +// The model and stepping fields will be interpreted as regular expressions +// An empty stepping field means 'any' stepping +type CPU struct { + MicroArchitecture string + Family string + Model string + Stepping string + Architecture string + MemoryChannelCount int + LogicalThreadCount int + CacheWayCount int +} + +var cpus = CPUDB{ + // Intel Core CPUs + {MicroArchitecture: "HSW", Family: "6", Model: "(50|69|70)", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Haswell + {MicroArchitecture: "BDW", Family: "6", Model: "(61|71)", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Broadwell + {MicroArchitecture: "SKL", Family: "6", Model: "(78|94)", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Skylake + {MicroArchitecture: "KBL", Family: "6", Model: "(142|158)", Stepping: "9", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Kabylake + {MicroArchitecture: "CFL", Family: "6", Model: "(142|158)", Stepping: "(10|11|12|13)", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Coffeelake + {MicroArchitecture: "RKL", Family: "6", Model: "167", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Rocket Lake + {MicroArchitecture: "TGL", Family: "6", Model: "(140|141)", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Tiger Lake + {MicroArchitecture: "ADL", Family: "6", Model: "(151|154)", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Alder Lake + {MicroArchitecture: "MTL", Family: "6", Model: "170", Stepping: "4", Architecture: "x86_64", MemoryChannelCount: 2, LogicalThreadCount: 2, CacheWayCount: 0}, // Meteor Lake + // Intel Xeon CPUs + {MicroArchitecture: "HSX", Family: "6", Model: "63", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 4, LogicalThreadCount: 2, CacheWayCount: 20}, // Haswell + {MicroArchitecture: "BDX", Family: "6", Model: "(79|86)", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 4, LogicalThreadCount: 2, CacheWayCount: 20}, // Broadwell + {MicroArchitecture: "SKX", Family: "6", Model: "85", Stepping: "(0|1|2|3|4)", Architecture: "x86_64", MemoryChannelCount: 6, LogicalThreadCount: 2, CacheWayCount: 11}, // Skylake + {MicroArchitecture: "CLX", Family: "6", Model: "85", Stepping: "(5|6|7)", Architecture: "x86_64", MemoryChannelCount: 6, LogicalThreadCount: 2, CacheWayCount: 11}, // Cascadelake + {MicroArchitecture: "CPX", Family: "6", Model: "85", Stepping: "11", Architecture: "x86_64", MemoryChannelCount: 6, LogicalThreadCount: 2, CacheWayCount: 11}, // Cooperlake + {MicroArchitecture: "ICX", Family: "6", Model: "(106|108)", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 12}, // Icelake + {MicroArchitecture: "SPR", Family: "6", Model: "143", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 15}, // Sapphire Rapids - generic + {MicroArchitecture: "SPR_MCC", Family: "6", Model: "143", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 15}, // Sapphire Rapids - MCC + {MicroArchitecture: "SPR_XCC", Family: "6", Model: "143", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 15}, // Sapphire Rapids - XCC + {MicroArchitecture: "EMR", Family: "6", Model: "207", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 15}, // Emerald Rapids - generic + {MicroArchitecture: "EMR_MCC", Family: "6", Model: "207", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 15}, // Emerald Rapids - MCC + {MicroArchitecture: "EMR_XCC", Family: "6", Model: "207", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 20}, // Emerald Rapids - XCC + {MicroArchitecture: "SRF", Family: "6", Model: "175", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 1, CacheWayCount: 12}, // Sierra Forest + {MicroArchitecture: "GNR", Family: "6", Model: "173", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 0, LogicalThreadCount: 2, CacheWayCount: 16}, // Granite Rapids - generic + {MicroArchitecture: "GNR_X1", Family: "6", Model: "173", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 16}, // Granite Rapids - SP (MCC/LCC) + {MicroArchitecture: "GNR_X2", Family: "6", Model: "173", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 16}, // Granite Rapids - SP (XCC) + {MicroArchitecture: "GNR_X3", Family: "6", Model: "173", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 12, LogicalThreadCount: 2, CacheWayCount: 16}, // Granite Rapids - AP (UCC) + {MicroArchitecture: "GNR_D", Family: "6", Model: "174", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 0, LogicalThreadCount: 2, CacheWayCount: 16}, // Granite Rapids - D (generic) + // AMD CPUs + {MicroArchitecture: "Naples", Family: "23", Model: "1", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 0}, // Naples + {MicroArchitecture: "Rome", Family: "23", Model: "49", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 0}, // Rome + {MicroArchitecture: "Milan", Family: "25", Model: "1", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 8, LogicalThreadCount: 2, CacheWayCount: 0}, // Milan + {MicroArchitecture: "Genoa", Family: "25", Model: "(1[6-9]|2[0-9]|3[01])", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 12, LogicalThreadCount: 2, CacheWayCount: 0}, // Genoa, model 16-31 + {MicroArchitecture: "Bergamo", Family: "25", Model: "(16[0-9]|17[0-5])", Stepping: "", Architecture: "x86_64", MemoryChannelCount: 12, LogicalThreadCount: 2, CacheWayCount: 0}, // Bergamo, model 160-175 + // ARM CPUs + {MicroArchitecture: "Neoverse N1", Family: "", Model: "1", Stepping: "r3p1", Architecture: "arm64", MemoryChannelCount: 8, LogicalThreadCount: 1, CacheWayCount: 0}, // AWS Graviton 2 + {MicroArchitecture: "Neoverse V1", Family: "", Model: "1", Stepping: "r1p1", Architecture: "arm64", MemoryChannelCount: 8, LogicalThreadCount: 1, CacheWayCount: 0}, // AWS Graviton 3 +} diff --git a/internal/cpudb/cpu_test.go b/internal/cpudb/cpu_test.go new file mode 100644 index 0000000..07b0782 --- /dev/null +++ b/internal/cpudb/cpu_test.go @@ -0,0 +1,178 @@ +package cpudb + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "testing" +) + +func TestGetCPU(t *testing.T) { + cpudb := NewCPUDB() + // should fail + _, err := cpudb.GetCPU("0", "0", "0", "", "", "") + if err == nil { + t.Fatal(err) + } + + cpu, err := cpudb.GetCPU("6", "85", "4", "", "", "") //SKX + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "SKX" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("6", "85", "7", "", "", "") //CLX + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "CLX" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("6", "85", "6", "", "", "") //CLX + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "CLX" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("6", "108", "", "", "", "0") //ICX + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "ICX" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("6", "71", "", "", "", "0") //BDW + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "BDW" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("6", "173", "", "", "2", "10") // GNR_X3 + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "GNR_X3" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 12 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("6", "173", "", "", "2", "8") // GNR_X2 + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "GNR_X2" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 8 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("6", "173", "", "", "2", "6") // GNR_X1 + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "GNR_X1" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 8 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("6", "173", "", "", "", "") // GNR with no differentiation + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "GNR" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("6", "207", "", "c0", "", "") // EMR XCC + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "EMR_XCC" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 8 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("6", "207", "", "40", "", "") // EMR MCC + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "EMR_MCC" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 8 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("6", "207", "", "", "", "") // EMR with no differentiation + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "EMR" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 8 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("25", "1", "", "", "", "") // Milan + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "Milan" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 8 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("25", "17", "", "", "", "") // Genoa + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "Genoa" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + if cpu.MemoryChannelCount != 12 { + t.Fatal(fmt.Errorf("Incorrect channel count: %d", cpu.MemoryChannelCount)) + } + + cpu, err = cpudb.GetCPU("6", "69", "99", "", "", "") //HSW + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "HSW" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("6", "70", "", "", "", "") //HSW + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "HSW" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } + + cpu, err = cpudb.GetCPU("", "1", "r3p1", "", "", "") // N1 + if err != nil { + t.Fatal(err) + } + if cpu.MicroArchitecture != "Neoverse N1" { + t.Fatal(fmt.Errorf("Found the wrong CPU: %s", cpu.MicroArchitecture)) + } +} diff --git a/internal/cpudb/cpudb.go b/internal/cpudb/cpudb.go new file mode 100644 index 0000000..01c6204 --- /dev/null +++ b/internal/cpudb/cpudb.go @@ -0,0 +1,198 @@ +/* +Package cpudb provides a reference of CPU architectures and identification keys for known CPUS. +*/ +package cpudb + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +type CPUDB []CPU + +// NewCPUDB initializes the CPUDB structure with the yaml and returns it +func NewCPUDB() *CPUDB { + return &cpus +} + +// GetCPU retrieves the CPU structure that matches the provided args +// capid4 needed to differentiate EMR MCC from EMR XCC +// +// capid4: $ lspci -s $(lspci | grep 325b | awk 'NR==1{{print $1}}') -xxx | awk '$1 ~ /^90/{{print $9 $8 $7 $6; exit}}' +// +// sockets and devices (eventually devices per socket) needed to differentiate GNR X1/2/3 +// +// devices: $ lspci -d 8086:3258 | wc -l +func (c *CPUDB) GetCPU(family, model, stepping, capid4, sockets, devices string) (cpu CPU, err error) { + for _, info := range *c { + // if family matches + if info.Family == family { + var reModel *regexp.Regexp + reModel, err = regexp.Compile(info.Model) + if err != nil { + return + } + // if model matches + if reModel.FindString(model) == model { + // if there is a stepping + if info.Stepping != "" { + var reStepping *regexp.Regexp + reStepping, err = regexp.Compile(info.Stepping) + if err != nil { + return + } + // if stepping does NOT match + if reStepping.FindString(stepping) == "" { + // no match + continue + } + } + cpu = info + if cpu.Family == "6" && (cpu.Model == "143" || cpu.Model == "207" || cpu.Model == "173") { // SPR, EMR, GNR + cpu, err = c.getSpecificCPU(family, model, capid4, sockets, devices) + } + return + } + } + } + err = fmt.Errorf("CPU match not found for family %s, model %s, stepping %s", family, model, stepping) + return +} + +func (c *CPUDB) GetCPUByMicroArchitecture(uarch string) (cpu CPU, err error) { + for _, info := range *c { + if strings.EqualFold(info.MicroArchitecture, uarch) { + cpu = info + return + } + } + err = fmt.Errorf("CPU match not found for uarch %s", uarch) + return +} + +func (c *CPUDB) getSpecificCPU(family, model, capid4, sockets, devices string) (cpu CPU, err error) { + if family == "6" && model == "143" { // SPR + cpu, err = c.getSPRCPU(capid4) + } else if family == "6" && model == "207" { // EMR + cpu, err = c.getEMRCPU(capid4) + } else if family == "6" && model == "173" { // GNR + cpu, err = c.getGNRCPU(sockets, devices) + } + return +} + +func (c *CPUDB) getSPRCPU(capid4 string) (cpu CPU, err error) { + var uarch string + if capid4 != "" { + var bits int64 + var capid4Int int64 + capid4Int, err = strconv.ParseInt(capid4, 16, 64) + if err != nil { + return + } + bits = (capid4Int >> 6) & 0b11 + if bits == 3 { + uarch = "SPR_XCC" + } else if bits == 1 { + uarch = "SPR_MCC" + } + } + if uarch == "" { + uarch = "SPR" + } + for _, info := range *c { + if info.MicroArchitecture == uarch { + cpu = info + return + } + } + err = fmt.Errorf("did not find matching SPR architecture in CPU database: %s", uarch) + return +} + +func (c *CPUDB) getEMRCPU(capid4 string) (cpu CPU, err error) { + var uarch string + if capid4 != "" { + var bits int64 + var capid4Int int64 + capid4Int, err = strconv.ParseInt(capid4, 16, 64) + if err != nil { + return + } + bits = (capid4Int >> 6) & 0b11 + if bits == 3 { + uarch = "EMR_XCC" + } else if bits == 1 { + uarch = "EMR_MCC" + } + } + if uarch == "" { + uarch = "EMR" + } + for _, info := range *c { + if info.MicroArchitecture == uarch { + cpu = info + return + } + } + err = fmt.Errorf("did not find matching EMR architecture in CPU database: %s", uarch) + return +} + +func (c *CPUDB) getGNRCPU(sockets, devices string) (cpu CPU, err error) { + var uarch string + if sockets != "" && devices != "" { + s, err := strconv.Atoi(sockets) + if err == nil && s != 0 { + d, err := strconv.Atoi(devices) + if err == nil && d != 0 { + devicesPerSocket := d / s + if devicesPerSocket == 3 { + uarch = "GNR_X1" + } else if devicesPerSocket == 4 { + uarch = "GNR_X2" + } else if devicesPerSocket == 5 { + uarch = "GNR_X3" + } + } + } + } + if uarch == "" { + uarch = "GNR" + } + for _, info := range *c { + if info.MicroArchitecture == uarch { + cpu = info + return + } + } + err = fmt.Errorf("did not find matching GNR architecture in CPU database: %s", uarch) + return +} + +func (cpu *CPU) GetCacheWays() (cacheWays []int64) { + wayCount := cpu.CacheWayCount + if wayCount == 0 { + return + } + var cacheSize int64 = 0 + // set wayCount bits in cacheSize + for i := 0; i < wayCount; i++ { + cacheSize = (cacheSize << 1) | 1 + } + var mask int64 = -1 // all bits set + for i := 0; i < wayCount; i++ { + // prepend the cache size to the list of ways + cacheWays = append([]int64{cacheSize}, cacheWays...) + // clear another low bit in mask + mask = mask << 1 + // mask lower bits (however many bits are cleared in mask var) + cacheSize = cacheSize & mask + } + return +} diff --git a/internal/progress/multispinner.go b/internal/progress/multispinner.go new file mode 100644 index 0000000..238ab4a --- /dev/null +++ b/internal/progress/multispinner.go @@ -0,0 +1,115 @@ +/* +Package progress provides CLI progress bar options. +*/ +package progress + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "os" + "time" + + "golang.org/x/term" +) + +var spinChars []string = []string{"ā£¾", "ā£½", "ā£»", "ā¢æ", "ā”æ", "ā£Ÿ", "ā£Æ", "ā£·"} + +type MultiSpinnerUpdateFunc func(string, string) error + +type spinnerState struct { + label string + status string + statusIsNew bool + spinIndex int +} + +type multiSpinner struct { + spinners []spinnerState + ticker *time.Ticker + done chan bool + spinning bool +} + +// NewMultiSpinner creates a new MultiSpinner +func NewMultiSpinner() *multiSpinner { + ms := multiSpinner{} + ms.done = make(chan bool) + return &ms +} + +// AddSpinner adds a spinner to the MultiSpinner +func (ms *multiSpinner) AddSpinner(label string) (err error) { + // make sure label is unique + for _, spinner := range ms.spinners { + if spinner.label == label { + err = fmt.Errorf("spinner with label %s already exists", label) + return + } + } + ms.spinners = append(ms.spinners, spinnerState{label, "?", false, 0}) + return +} + +// Start starts the spinner +func (ms *multiSpinner) Start() { + ms.ticker = time.NewTicker(250 * time.Millisecond) + ms.spinning = true + go ms.onTick() +} + +// Finish stops the spinner +func (ms *multiSpinner) Finish() { + if ms.spinning { + ms.ticker.Stop() + ms.done <- true + ms.draw(false) + ms.spinning = false + } +} + +// Status updates the status of a spinner +func (ms *multiSpinner) Status(label string, status string) (err error) { + for spinnerIdx, spinner := range ms.spinners { + if spinner.label == label { + if status != spinner.status { + ms.spinners[spinnerIdx].status = status + ms.spinners[spinnerIdx].statusIsNew = true + } + return + } + } + err = fmt.Errorf("did not find spinner with label %s", label) + return +} + +func (ms *multiSpinner) onTick() { + for { + select { + case <-ms.done: + return + case <-ms.ticker.C: + ms.draw(true) + } + } +} + +func (ms *multiSpinner) draw(goUp bool) { + for i, spinner := range ms.spinners { + if !term.IsTerminal(int(os.Stderr.Fd())) && !spinner.statusIsNew { + continue + } + fmt.Fprintf(os.Stderr, "%-20s %s %-40s\n", spinner.label, spinChars[spinner.spinIndex], spinner.status) + ms.spinners[i].statusIsNew = false + ms.spinners[i].spinIndex += 1 + if ms.spinners[i].spinIndex >= len(spinChars) { + ms.spinners[i].spinIndex = 0 + } + } + if goUp && term.IsTerminal(int(os.Stderr.Fd())) { + for range ms.spinners { + fmt.Fprintf(os.Stderr, "\x1b[1A") + } + } +} diff --git a/internal/progress/multispinner_test.go b/internal/progress/multispinner_test.go new file mode 100644 index 0000000..9c15d55 --- /dev/null +++ b/internal/progress/multispinner_test.go @@ -0,0 +1,43 @@ +package progress + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "testing" +) + +func TestNewMultiSpinner(t *testing.T) { + spinner := NewMultiSpinner() + if spinner == nil { + t.Fatal("failed to create a spinner") + } +} + +func TestMultiSpinner(t *testing.T) { + spinner := NewMultiSpinner() + if spinner == nil { + t.Fatal("failed to create a spinner") + } + if spinner.AddSpinner("A") != nil { + t.Fatal("failed to add spinner") + } + if spinner.AddSpinner("B") != nil { + t.Fatal("failed to add spinner") + } + if spinner.AddSpinner("A") == nil { + t.Fatal("added spinner with same label") + } + spinner.Start() + + if spinner.Status("A", "FOO") != nil { + t.Fatal("failed to update spinner status") + } + if spinner.Status("B", "BAR") != nil { + t.Fatal("failed to update spinner status") + } + if spinner.Status("C", "WOOPS") == nil { + t.Fatal("updated status of non-existent spinner") + } + spinner.Finish() +} diff --git a/internal/report/accelerator_defs.go b/internal/report/accelerator_defs.go new file mode 100644 index 0000000..5da6917 --- /dev/null +++ b/internal/report/accelerator_defs.go @@ -0,0 +1,61 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// Intel Accelerators (sorted by devid) +// references: +// https://pci-ids.ucw.cz/read/PC/8086 + +type Accelerator struct { + MfgID string + DevID string + Name string + FullName string + Description string +} + +var accelDefs = []Accelerator{ + { + MfgID: "8086", + DevID: "(2710|2714)", + Name: "DLB", + FullName: "Intel Dynamic Load Balancer", + Description: "hardware managed system of queues and arbiters connecting producers and consumers", + }, + { + MfgID: "8086", + DevID: "B25", + Name: "DSA", + FullName: "Intel Data Streaming Accelerator", + Description: "a high-performance data copy and transformation accelerator", + }, + { + MfgID: "8086", + DevID: "CFE", + Name: "IAA", + FullName: "Intel Analytics Accelerator", + Description: "accelerates compression and decompression for big data applications and in-memory analytic databases", + }, + { + MfgID: "8086", + DevID: "(4940|4942|4944)", + Name: "QAT (on CPU)", + FullName: "Intel Quick Assist Technology", + Description: "accelerates data encryption and compression for applications from networking to enterprise, cloud to storage, and content delivery to database", + }, + { + MfgID: "8086", + DevID: "37C8", + Name: "QAT (on chipset)", + FullName: "Intel Quick Assist Technology", + Description: "accelerates data encryption and compression for applications from networking to enterprise, cloud to storage, and content delivery to database", + }, + { + MfgID: "8086", + DevID: "57C2", + Name: "vRAN Boost", + FullName: "Intel vRAN Boost", + Description: "accelerates vRAN workloads", + }, +} diff --git a/internal/report/benchmarking_table_helpers.go b/internal/report/benchmarking_table_helpers.go new file mode 100644 index 0000000..c0a0ef4 --- /dev/null +++ b/internal/report/benchmarking_table_helpers.go @@ -0,0 +1,189 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "log/slog" + "perfspect/internal/script" + "perfspect/internal/util" + "regexp" + "strconv" + "strings" +) + +func cpuSpeedFromOutput(outputs map[string]script.ScriptOutput) string { + var vals []float64 + for _, line := range strings.Split(strings.TrimSpace(outputs[script.CpuSpeedScriptName].Stdout), "\n") { + tokens := strings.Split(line, " ") + if len(tokens) != 2 { + slog.Error("unexpected stress-ng output format", slog.String("line", line)) + return "" + } + fv, err := strconv.ParseFloat(tokens[1], 64) + if err != nil { + slog.Error("unexpected value in 2nd token (%s), expected float in line: %s", slog.String("token", tokens[1]), slog.String("line", line)) + return "" + } + vals = append(vals, fv) + } + if len(vals) == 0 { + slog.Warn("no values detected in stress-ng output") + return "" + } + return fmt.Sprintf("%.0f", util.GeoMean(vals)) +} + +func ParseTurbostatOutput(output string) (singleCoreTurbo, allCoreTurbo, turboPower, turboTemperature string) { + var allTurbos []string + var allTDPs []string + var allTemps []string + var turbos []string + var tdps []string + var temps []string + var headers []string + idxTurbo := -1 + idxTdp := -1 + idxTemp := -1 + re := regexp.MustCompile(`\s+`) // whitespace + for _, line := range strings.Split(output, "\n") { + if strings.TrimSpace(line) == "" { + continue + } + if strings.Contains(line, "stress-ng") { + if strings.Contains(line, "completed") { + if idxTurbo >= 0 && len(allTurbos) >= 2 { + turbos = append(turbos, allTurbos[len(allTurbos)-2]) + allTurbos = nil + } + if idxTdp >= 0 && len(allTDPs) >= 2 { + tdps = append(tdps, allTDPs[len(allTDPs)-2]) + allTDPs = nil + } + if idxTemp >= 0 && len(allTemps) >= 2 { + temps = append(temps, allTemps[len(allTemps)-2]) + allTemps = nil + } + } + continue + } + if strings.Contains(line, "Package") || strings.Contains(line, "CPU") || strings.Contains(line, "Core") || strings.Contains(line, "Node") { + headers = re.Split(line, -1) // split by whitespace + for i, h := range headers { + if h == "Bzy_MHz" { + idxTurbo = i + } else if h == "PkgWatt" { + idxTdp = i + } else if h == "PkgTmp" { + idxTemp = i + } + } + continue + } + tokens := re.Split(line, -1) + if idxTurbo >= 0 { + allTurbos = append(allTurbos, tokens[idxTurbo]) + } + if idxTdp >= 0 { + allTDPs = append(allTDPs, tokens[idxTdp]) + } + if idxTemp >= 0 { + allTemps = append(allTemps, tokens[idxTemp]) + } + } + if len(turbos) == 2 { + singleCoreTurbo = turbos[0] + " MHz" + allCoreTurbo = turbos[1] + " MHz" + } + if len(tdps) == 2 { + turboPower = tdps[1] + " Watts" + } + if len(temps) == 2 { + turboTemperature = temps[1] + " C" + } + return +} + +func maxPowerFromOutput(outputs map[string]script.ScriptOutput) string { + _, _, power, _ := ParseTurbostatOutput(outputs[script.TurboFrequencyPowerAndTemperatureScriptName].Stdout) + return power +} +func minPowerFromOutput(outputs map[string]script.ScriptOutput) string { + watts := strings.TrimSpace(outputs[script.IdlePowerScriptName].Stdout) + if watts == "" || watts == "0.00" { + return "" + } + return watts + " Watts" +} +func maxTemperatureFromOutput(outputs map[string]script.ScriptOutput) string { + _, _, _, temperature := ParseTurbostatOutput(outputs[script.TurboFrequencyPowerAndTemperatureScriptName].Stdout) + return temperature +} + +// Sample avx-turbo output +// ... +// Will test up to 64 CPUs +// Cores | ID | Description | OVRLP3 | Mops | A/M-ratio | A/M-MHz | M/tsc-ratio +// 1 | scalar_iadd | Scalar integer adds | 1.000 | 3901 | 1.95 | 3900 | 1.00 +// 1 | avx128_fma | 128-bit serial DP FMAs | 1.000 | 974 | 1.95 | 3900 | 1.00 +// 1 | avx256_fma | 256-bit serial DP FMAs | 1.000 | 974 | 1.95 | 3900 | 1.00 +// 1 | avx512_fma | 512-bit serial DP FMAs | 1.000 | 974 | 1.95 | 3900 | 1.00 + +// Cores | ID | Description | OVRLP3 | Mops | A/M-ratio | A/M-MHz | M/tsc-ratio +// 2 | scalar_iadd | Scalar integer adds | 1.000 | 3901, 3901 | 1.95, 1.95 | 3900, 3900 | 1.00, 1.00 +// 2 | avx128_fma | 128-bit serial DP FMAs | 1.000 | 974, 974 | 1.95, 1.95 | 3900, 3900 | 1.00, 1.00 +// 2 | avx256_fma | 256-bit serial DP FMAs | 1.000 | 974, 974 | 1.95, 1.95 | 3900, 3900 | 1.00, 1.00 +// 2 | avx512_fma | 512-bit serial DP FMAs | 1.000 | 974, 974 | 1.95, 1.95 | 3900, 3900 | 1.00, 1.00 + +// Cores | ID | Description | OVRLP3 | Mops | A/M-ratio | A/M-MHz | M/tsc-ratio +// 3 | scalar_iadd | Scalar integer adds | 1.000 | 3900, 3901, 3901 | 1.95, 1.95, 1.95 | 3900, 3900, 3900 | 1.00, 1.00, 1.00 +// 3 | avx128_fma | 128-bit serial DP FMAs | 1.000 | 974, 975, 975 | 1.95, 1.95, 1.95 | 3900, 3900, 3900 | 1.00, 1.00, 1.00 +// 3 | avx256_fma | 256-bit serial DP FMAs | 1.000 | 974, 975, 975 | 1.95, 1.95, 1.95 | 3900, 3900, 3900 | 1.00, 1.00, 1.00 +// 3 | avx512_fma | 512-bit serial DP FMAs | 1.000 | 974, 975, 974 | 1.95, 1.95, 1.95 | 3900, 3900, 3900 | 1.00, 1.00, 1.00 +// ... +func avxTurboFrequenciesFromOutput(output string) (nonavxFreqs, avx128Freqs, avx256Freqs, avx512Freqs []float64, err error) { + started := false + for _, line := range strings.Split(output, "\n") { + if strings.HasPrefix(line, "Cores | ID") { + started = true + continue + } + if !started { + continue + } + if line == "" { + started = false + continue + } + fields := strings.Split(line, "|") + if len(fields) < 7 { + err = fmt.Errorf("avx-turbo unable to measure frequencies") + return + } + freqs := strings.Split(fields[6], ",") + var sumFreqs float64 + for _, freq := range freqs { + var f float64 + f, err = strconv.ParseFloat(strings.TrimSpace(freq), 64) + if err != nil { + return + } + sumFreqs += f + } + avgFreq := sumFreqs / float64(len(freqs)) + if strings.Contains(fields[1], "scalar_iadd") { + nonavxFreqs = append(nonavxFreqs, avgFreq/1000.0) + } else if strings.Contains(fields[1], "avx128_fma") { + avx128Freqs = append(avx128Freqs, avgFreq/1000.0) + } else if strings.Contains(fields[1], "avx256_fma") { + avx256Freqs = append(avx256Freqs, avgFreq/1000.0) + } else if strings.Contains(fields[1], "avx512_fma") { + avx512Freqs = append(avx512Freqs, avgFreq/1000.0) + } else { + err = fmt.Errorf("unexpected data from avx-turbo, unknown instruction type") + return + } + } + return +} diff --git a/internal/report/dimm_table_helpers.go b/internal/report/dimm_table_helpers.go new file mode 100644 index 0000000..2e4f892 --- /dev/null +++ b/internal/report/dimm_table_helpers.go @@ -0,0 +1,655 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "log" + "log/slog" + "perfspect/internal/script" + "regexp" + "strconv" + "strings" +) + +const ( + BankLocatorIdx = iota + LocatorIdx + ManufacturerIdx + PartIdx + SerialIdx + SizeIdx + TypeIdx + DetailIdx + SpeedIdx + RankIdx + ConfiguredSpeedIdx + DerivedSocketIdx + DerivedChannelIdx + DerivedSlotIdx +) + +func dimmInfoFromDmiDecode(dmiDecodeOutput string) [][]string { + return valsArrayFromDmiDecodeRegexSubmatch( + dmiDecodeOutput, + "17", + `^Bank Locator:\s*(.+?)$`, + `^Locator:\s*(.+?)$`, + `^Manufacturer:\s*(.+?)$`, + `^Part Number:\s*(.+?)\s*$`, + `^Serial Number:\s*(.+?)\s*$`, + `^Size:\s*(.+?)$`, + `^Type:\s*(.+?)$`, + `^Type Detail:\s*(.+?)$`, + `^Speed:\s*(.+?)$`, + `^Rank:\s*(.+?)$`, + `^Configured.*Speed:\s*(.+?)$`, + ) +} + +func installedMemoryFromOutput(outputs map[string]script.ScriptOutput) string { + dimmInfo := dimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout) + dimmTypeCount := make(map[string]int) + for _, dimm := range dimmInfo { + dimmKey := dimm[TypeIdx] + ":" + dimm[SizeIdx] + ":" + dimm[SpeedIdx] + ":" + dimm[ConfiguredSpeedIdx] + if count, ok := dimmTypeCount[dimmKey]; ok { + dimmTypeCount[dimmKey] = count + 1 + } else { + dimmTypeCount[dimmKey] = 1 + } + } + var summaries []string + re := regexp.MustCompile(`(\d+)\s*(\w*)`) + for dimmKey, count := range dimmTypeCount { + fields := strings.Split(dimmKey, ":") + match := re.FindStringSubmatch(fields[1]) // size field + if match != nil { + size, err := strconv.Atoi(match[1]) + if err != nil { + log.Printf("Don't recognize DIMM size format: %s", fields[1]) + return "" + } + sum := count * size + unit := match[2] + dimmType := fields[0] + speed := fields[2] + configuredSpeed := fields[3] + summary := fmt.Sprintf("%d%s (%dx%d%s %s %s [%s])", sum, unit, count, size, unit, dimmType, speed, configuredSpeed) + summaries = append(summaries, summary) + } + } + return strings.Join(summaries, "; ") +} + +func populatedChannelsFromOutput(outputs map[string]script.ScriptOutput) string { + channelsMap := make(map[string]bool) + dimmInfo := dimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout) + derivedDimmFields := derivedDimmsFieldFromOutput(outputs) + if len(derivedDimmFields) != len(dimmInfo) { + slog.Error("derivedDimmFields and dimmInfo have different lengths", slog.Int("derivedDimmFields", len(derivedDimmFields)), slog.Int("dimmInfo", len(dimmInfo))) + return "" + } + for i, dimm := range dimmInfo { + if !strings.Contains(dimm[SizeIdx], "No") { + channelsMap[derivedDimmFields[i].socket+","+derivedDimmFields[i].channel] = true + } + } + if len(channelsMap) > 0 { + return fmt.Sprintf("%d", len(channelsMap)) + } + return "" +} + +type derivedFields struct { + socket string + channel string + slot string +} + +// derivedDimmsFieldFromOutput returns a slice of derived fields from the output of a script. +func derivedDimmsFieldFromOutput(outputs map[string]script.ScriptOutput) []derivedFields { + dimmInfo := dimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout) + var derivedFields []derivedFields + var err error + channels := channelsFromOutput(outputs) + numChannels, err := strconv.Atoi(channels) + if err != nil || numChannels == 0 { + return nil + } + platformVendor := valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "0", `Vendor:\s*(.*)`) + numSockets, err := strconv.Atoi(valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(.*:\s*(.+?)$`)) + if err != nil || numSockets == 0 { + return nil + } + success := false + if strings.Contains(platformVendor, "Dell") { + derivedFields, err = deriveDIMMInfoDell(dimmInfo, numChannels) + if err != nil { + slog.Info("failed to parse dimm info on Dell platform", slog.String("error", err.Error())) + } + success = err == nil + } else if platformVendor == "HPE" { + derivedFields, err = deriveDIMMInfoHPE(dimmInfo, numSockets, numChannels) + if err != nil { + slog.Info("failed to parse dimm info on HPE platform", slog.String("error", err.Error())) + } + success = err == nil + } else if platformVendor == "Amazon EC2" { + derivedFields, err = deriveDIMMInfoEC2(dimmInfo, numChannels) + if err != nil { + slog.Info("failed to parse dimm info on Amazon EC2 platform", slog.String("error", err.Error())) + } + success = err == nil + } + if !success { + derivedFields, err = deriveDIMMInfoOther(dimmInfo, numChannels) + if err != nil { + slog.Info("failed to parse dimm info on other platform", slog.String("error", err.Error())) + } + } + return derivedFields +} + +/* as seen on 2 socket Dell systems... +* "Bank Locator" for all DIMMs is "Not Specified" and "Locator" is A1-A12 and B1-B12. +* A1 and A7 are channel 0, A2 and A8 are channel 1, etc. + */ +func deriveDIMMInfoDell(dimms [][]string, channelsPerSocket int) ([]derivedFields, error) { + derivedFields := make([]derivedFields, len(dimms)) + re := regexp.MustCompile(`([ABCD])([1-9]\d*)`) + for i, dimm := range dimms { + if !strings.Contains(dimm[BankLocatorIdx], "Not Specified") { + err := fmt.Errorf("doesn't conform to expected Dell Bank Locator format") + return nil, err + } + match := re.FindStringSubmatch(dimm[LocatorIdx]) + if match == nil { + err := fmt.Errorf("doesn't conform to expected Dell Locator format") + return nil, err + } + alpha := match[1] + var numeric int + numeric, err := strconv.Atoi(match[2]) + if err != nil { + err = fmt.Errorf("doesn't conform to expected Dell Locator numeric format") + return nil, err + } + // Socket + // A = 0, B = 1, C = 2, D = 3 + derivedFields[i].socket = fmt.Sprintf("%d", int(alpha[0])-int('A')) + // Slot + if numeric <= channelsPerSocket { + derivedFields[i].slot = "0" + } else { + derivedFields[i].slot = "1" + } + // Channel + if numeric <= channelsPerSocket { + derivedFields[i].channel = fmt.Sprintf("%d", numeric-1) + } else { + derivedFields[i].channel = fmt.Sprintf("%d", numeric-(channelsPerSocket+1)) + } + } + return derivedFields, nil +} + +/* as seen on Amazon EC2 bare-metal systems... + * BANK LOC LOCATOR + * c5.metal + * NODE 1 DIMM_A0 + * NODE 1 DIMM_A1 + * ... + * NODE 2 DIMM_G0 + * NODE 2 DIMM_G1 + * ... <<< there's no 'I' + * NODE 2 DIMM_M0 + * NODE 2 DIMM_M1 + * + * c6i.metal + * NODE 0 CPU0 Channel0 DIMM0 + * NODE 0 CPU0 Channel0 DIMM1 + * NODE 0 CPU0 Channel1 DIMM0 + * NODE 0 CPU0 Channel1 DIMM1 + * ... + * NODE 7 CPU1 Channel7 DIMM0 + * NODE 7 CPU1 Channel7 DIMM1 + */ +func deriveDIMMInfoEC2(dimms [][]string, channelsPerSocket int) ([]derivedFields, error) { + derivedFields := make([]derivedFields, len(dimms)) + c5bankLocRe := regexp.MustCompile(`NODE\s+([1-9])`) + c5locRe := regexp.MustCompile(`DIMM_(.)(.)`) + c6ibankLocRe := regexp.MustCompile(`NODE\s+(\d+)`) + c6ilocRe := regexp.MustCompile(`CPU(\d+)\s+Channel(\d+)\s+DIMM(\d+)`) + for i, dimm := range dimms { + // try c5.metal format + bankLocMatch := c5bankLocRe.FindStringSubmatch(dimm[BankLocatorIdx]) + locMatch := c5locRe.FindStringSubmatch(dimm[LocatorIdx]) + if locMatch != nil && bankLocMatch != nil { + var socket, channel, slot int + socket, _ = strconv.Atoi(bankLocMatch[1]) + socket -= 1 + if int(locMatch[1][0]) < int('I') { // there is no 'I' + channel = (int(locMatch[1][0]) - int('A')) % channelsPerSocket + } else if int(locMatch[1][0]) > int('I') { + channel = (int(locMatch[1][0]) - int('B')) % channelsPerSocket + } else { + err := fmt.Errorf("doesn't conform to expected EC2 format") + return nil, err + } + slot, _ = strconv.Atoi(locMatch[2]) + derivedFields[i].socket = fmt.Sprintf("%d", socket) + derivedFields[i].channel = fmt.Sprintf("%d", channel) + derivedFields[i].slot = fmt.Sprintf("%d", slot) + continue + } + // try c6i.metal format + bankLocMatch = c6ibankLocRe.FindStringSubmatch(dimm[BankLocatorIdx]) + locMatch = c6ilocRe.FindStringSubmatch(dimm[LocatorIdx]) + if locMatch != nil && bankLocMatch != nil { + var socket, channel, slot int + socket, _ = strconv.Atoi(locMatch[1]) + channel, _ = strconv.Atoi(locMatch[2]) + slot, _ = strconv.Atoi(locMatch[3]) + derivedFields[i].socket = fmt.Sprintf("%d", socket) + derivedFields[i].channel = fmt.Sprintf("%d", channel) + derivedFields[i].slot = fmt.Sprintf("%d", slot) + continue + } + err := fmt.Errorf("doesn't conform to expected EC2 format") + return nil, err + } + return derivedFields, nil +} + +/* as seen on 2 socket HPE systems...2 slots per channel +* Locator field has these: PROC 1 DIMM 1, PROC 1 DIMM 2, etc... +* DIMM/slot numbering on board follows logic shown below + */ +func deriveDIMMInfoHPE(dimms [][]string, numSockets int, channelsPerSocket int) ([]derivedFields, error) { + derivedFields := make([]derivedFields, len(dimms)) + slotsPerChannel := len(dimms) / (numSockets * channelsPerSocket) + re := regexp.MustCompile(`PROC ([1-9]\d*) DIMM ([1-9]\d*)`) + for i, dimm := range dimms { + if !strings.Contains(dimm[BankLocatorIdx], "Not Specified") { + err := fmt.Errorf("doesn't conform to expected HPE Bank Locator format: %s", dimm[BankLocatorIdx]) + return nil, err + } + match := re.FindStringSubmatch(dimm[LocatorIdx]) + if match == nil { + err := fmt.Errorf("doesn't conform to expected HPE Locator format: %s", dimm[LocatorIdx]) + return nil, err + } + socket, err := strconv.Atoi(match[1]) + if err != nil { + err := fmt.Errorf("failed to parse socket number: %s", match[1]) + return nil, err + } + socket -= 1 + derivedFields[i].socket = fmt.Sprintf("%d", socket) + dimmNum, err := strconv.Atoi(match[2]) + if err != nil { + err := fmt.Errorf("failed to parse DIMM number: %s", match[2]) + return nil, err + } + channel := (dimmNum - 1) / slotsPerChannel + derivedFields[i].channel = fmt.Sprintf("%d", channel) + var slot int + if (dimmNum < channelsPerSocket && dimmNum%2 != 0) || (dimmNum > channelsPerSocket && dimmNum%2 == 0) { + slot = 0 + } else { + slot = 1 + } + derivedFields[i].slot = fmt.Sprintf("%d", slot) + } + return derivedFields, nil +} + +/* +Get DIMM socket and slot from Bank Locator or Locator field from dmidecode. +This method is inherently unreliable/incomplete as each OEM can set +these fields as they see fit. +Returns None when there's no match. +*/ +func getDIMMSocketSlot(dimmType DIMMType, reBankLoc *regexp.Regexp, reLoc *regexp.Regexp, bankLocator string, locator string) (socket int, slot int, err error) { + if dimmType == DIMMType0 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + } + return + } else if dimmType == DIMMType1 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + return + } + } else if dimmType == DIMMType2 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + return + } + } else if dimmType == DIMMType3 { + match := reBankLoc.FindStringSubmatch(bankLocator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + return + } + } else if dimmType == DIMMType4 { + match := reBankLoc.FindStringSubmatch(bankLocator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[4]) + return + } + } else if dimmType == DIMMType5 { + match := reBankLoc.FindStringSubmatch(bankLocator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + return + } + } else if dimmType == DIMMType6 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + socket -= 1 + slot, _ = strconv.Atoi(match[3]) + slot -= 1 + return + } + } else if dimmType == DIMMType7 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + slot -= 1 + return + } + } else if dimmType == DIMMType8 { + match := reBankLoc.FindStringSubmatch(bankLocator) + if match != nil { + match2 := reLoc.FindStringSubmatch(locator) + if match2 != nil { + socket, _ = strconv.Atoi(match[1]) + socket -= 1 + slot, _ = strconv.Atoi(match2[2]) + slot -= 1 + return + } + } + } else if dimmType == DIMMType9 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[2]) + return + } + } else if dimmType == DIMMType10 { + match := reBankLoc.FindStringSubmatch(bankLocator) + if match != nil { + socket = 0 + slot, _ = strconv.Atoi(match[2]) + return + } + } else if dimmType == DIMMType11 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket = 0 + slot, _ = strconv.Atoi(match[2]) + return + } + } else if dimmType == DIMMType12 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + socket = socket - 1 + slot, _ = strconv.Atoi(match[3]) + slot = slot - 1 + return + } + } else if dimmType == DIMMType13 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + slot = slot - 1 + return + } + } else if dimmType == DIMMType14 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot = 0 + return + } + } else if dimmType == DIMMType15 { + match := reLoc.FindStringSubmatch(locator) + if match != nil { + socket, _ = strconv.Atoi(match[1]) + slot, _ = strconv.Atoi(match[3]) + return + } + } + err = fmt.Errorf("unrecognized bank locator and/or locator in dimm info: %s %s", bankLocator, locator) + return +} + +type DIMMType int + +const ( + DIMMTypeUNKNOWN = -1 + DIMMType0 DIMMType = iota + DIMMType1 + DIMMType2 + DIMMType3 + DIMMType4 + DIMMType5 + DIMMType6 + DIMMType7 + DIMMType8 + DIMMType9 + DIMMType10 + DIMMType11 + DIMMType12 + DIMMType13 + DIMMType14 + DIMMType15 +) + +func getDIMMParseInfo(bankLocator string, locator string) (dimmType DIMMType, reBankLoc *regexp.Regexp, reLoc *regexp.Regexp) { + dimmType = DIMMTypeUNKNOWN + // Inspur ICX 2s system + // Needs to be before next regex pattern to differentiate + reLoc = regexp.MustCompile(`CPU([0-9])_C([0-9])D([0-9])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType0 + return + } + reLoc = regexp.MustCompile(`CPU([0-9])_([A-Z])([0-9])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType1 + return + } + reLoc = regexp.MustCompile(`CPU([0-9])_MC._DIMM_([A-Z])([0-9])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType2 + return + } + reBankLoc = regexp.MustCompile(`NODE ([0-9]) CHANNEL ([0-9]) DIMM ([0-9])`) + if reBankLoc.FindStringSubmatch(bankLocator) != nil { + dimmType = DIMMType3 + return + } + /* Added for SuperMicro X13DET-B (SPR). Must be before Type4 because Type4 matches, but data in BankLoc is invalid. + * Locator: P1-DIMMA1 + * Locator: P1-DIMMB1 + * Locator: P1-DIMMC1 + * ... + * Locator: P2-DIMMA1 + * ... + * Note: also matches SuperMicro X11DPT-B (CLX) + */ + reLoc = regexp.MustCompile(`P([1,2])-DIMM([A-L])([1,2])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType12 + return + } + reBankLoc = regexp.MustCompile(`P([0-9])_Node([0-9])_Channel([0-9])_Dimm([0-9])`) + if reBankLoc.FindStringSubmatch(bankLocator) != nil { + dimmType = DIMMType4 + return + } + reBankLoc = regexp.MustCompile(`_Node([0-9])_Channel([0-9])_Dimm([0-9])`) + if reBankLoc.FindStringSubmatch(bankLocator) != nil { + dimmType = DIMMType5 + return + } + /* SKX SDP + * Locator: CPU1_DIMM_A1, Bank Locator: NODE 1 + * Locator: CPU1_DIMM_A2, Bank Locator: NODE 1 + */ + reLoc = regexp.MustCompile(`CPU([1-4])_DIMM_([A-Z])([1-2])`) + if reLoc.FindStringSubmatch(locator) != nil { + reBankLoc = regexp.MustCompile(`NODE ([1-8])`) + if reBankLoc.FindStringSubmatch(bankLocator) != nil { + dimmType = DIMMType6 + return + } + } + /* ICX SDP + * Locator: CPU0_DIMM_A1, Bank Locator: NODE 0 + * Locator: CPU0_DIMM_A2, Bank Locator: NODE 0 + */ + reLoc = regexp.MustCompile(`CPU([0-7])_DIMM_([A-Z])([1-2])`) + if reLoc.FindStringSubmatch(locator) != nil { + reBankLoc = regexp.MustCompile(`NODE ([0-9]+)`) + if reBankLoc.FindStringSubmatch(bankLocator) != nil { + dimmType = DIMMType7 + return + } + } + reBankLoc = regexp.MustCompile(`NODE ([1-9]\d*)`) + if reBankLoc.FindStringSubmatch(bankLocator) != nil { + reLoc = regexp.MustCompile(`DIMM_([A-Z])([1-9]\d*)`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType8 + return + } + } + /* GIGABYTE MILAN + * Locator: DIMM_P0_A0, Bank Locator: BANK 0 + * Locator: DIMM_P0_A1, Bank Locator: BANK 1 + * Locator: DIMM_P0_B0, Bank Locator: BANK 0 + * ... + * Locator: DIMM_P1_I0, Bank Locator: BANK 0 + */ + reLoc = regexp.MustCompile(`DIMM_P([0-1])_[A-Z]([0-1])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType9 + return + } + /* my NUC + * Locator: SODIMM0, Bank Locator: CHANNEL A DIMM0 + * Locator: SODIMM1, Bank Locator: CHANNEL B DIMM0 + */ + reBankLoc = regexp.MustCompile(`CHANNEL ([A-D]) DIMM([0-9])`) + if reBankLoc.FindStringSubmatch(bankLocator) != nil { + dimmType = DIMMType10 + return + } + /* Alder Lake Client Desktop + * Locator: Controller0-ChannelA-DIMM0, Bank Locator: BANK 0 + * Locator: Controller1-ChannelA-DIMM0, Bank Locator: BANK 0 + */ + reLoc = regexp.MustCompile(`Controller([0-1]).*DIMM([0-1])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType11 + return + } + /* BIRCHSTREAM + * LOCATOR BANK LOCATOR + * CPU0_DIMM_A1 BANK 0 + * CPU0_DIMM_A2 BANK 0 + * CPU0_DIMM_B1 BANK 1 + * CPU0_DIMM_B2 BANK 1 + * ... + * CPU0_DIMM_H2 BANK 7 + */ + reLoc = regexp.MustCompile(`CPU([\d])_DIMM_([A-H])([1-2])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType13 + return + } + /* BIRCHSTREAM GRANITE RAPIDS AP/X3 + * LOCATOR BANK LOCATOR + * CPU0_DIMM_A BANK 0 + * CPU0_DIMM_B BANK 1 + * CPU0_DIMM_C BANK 2 + * CPU0_DIMM_D BANK 3 + * ... + * CPU0_DIMM_L BANK 11 + */ + reLoc = regexp.MustCompile(`CPU([\d])_DIMM_([A-L])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType14 + return + } + /* FOREST CITY PLATFORM FOR SRF AND GNR + * LOCATOR BANK LOCATOR + * CPU0 CH0/D0 BANK 0 + * CPU0 CH0/D1 BANK 0 + * CPU0 CH1/D0 BANK 1 + * CPU0 CH1/D1 BANK 1 + * ... + * CPU0 CH7/D1 BANK 7 + */ + reLoc = regexp.MustCompile(`CPU([\d]) CH([0-7])/D([0-1])`) + if reLoc.FindStringSubmatch(locator) != nil { + dimmType = DIMMType15 + return + } + return +} + +func deriveDIMMInfoOther(dimms [][]string, channelsPerSocket int) ([]derivedFields, error) { + derivedFields := make([]derivedFields, len(dimms)) + previousSocket, channel := -1, 0 + if len(dimms) == 0 { + err := fmt.Errorf("no DIMMs") + return nil, err + } + dimmType, reBankLoc, reLoc := getDIMMParseInfo((dimms)[0][BankLocatorIdx], (dimms)[0][LocatorIdx]) + if dimmType == DIMMTypeUNKNOWN { + err := fmt.Errorf("unknown DIMM identification format") + return nil, err + } + for i, dimm := range dimms { + var socket, slot int + socket, slot, err := getDIMMSocketSlot(dimmType, reBankLoc, reLoc, dimm[BankLocatorIdx], dimm[LocatorIdx]) + if err != nil { + slog.Info("Couldn't extract socket and slot from DIMM info", slog.String("error", err.Error())) + return nil, nil + } + if socket > previousSocket { + channel = 0 + } else if previousSocket == socket && slot == 0 { + channel++ + } + // sanity check + if channel >= channelsPerSocket { + err := fmt.Errorf("invalid interpretation of DIMM data") + return nil, err + } + previousSocket = socket + derivedFields[i].socket = fmt.Sprintf("%d", socket) + derivedFields[i].channel = fmt.Sprintf("%d", channel) + derivedFields[i].slot = fmt.Sprintf("%d", slot) + } + return derivedFields, nil +} diff --git a/internal/report/gpu_defs.go b/internal/report/gpu_defs.go new file mode 100644 index 0000000..589bf2e --- /dev/null +++ b/internal/report/gpu_defs.go @@ -0,0 +1,125 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// Intel Discrete GPUs (sorted by devid) +// references: +// https://pci-ids.ucw.cz/read/PC/8086 +// https://dgpu-docs.intel.com/devices/hardware-table.html +// +// The devid field will be interpreted as a regular expression. + +type GPUDef struct { + Model string + MfgID string + DevID string +} + +var IntelGPUs = []GPUDef{ + { + Model: "ATS-P", + MfgID: "8086", + DevID: "201", + }, + { + Model: "Ponte Vecchio 2T", + MfgID: "8086", + DevID: "BD0", + }, + { + Model: "Ponte Vecchio 1T", + MfgID: "8086", + DevID: "BD5", + }, + { + Model: "IntelĀ® IrisĀ® Xe MAX Graphics (DG1)", + MfgID: "8086", + DevID: "4905", + }, + { + Model: "IntelĀ® IrisĀ® Xe Pod (DG1)", + MfgID: "8086", + DevID: "4906", + }, + { + Model: "SG1", + MfgID: "8086", + DevID: "4907", + }, + { + Model: "IntelĀ® IrisĀ® Xe Graphics (DG1)", + MfgID: "8086", + DevID: "4908", + }, + { + Model: "IntelĀ® IrisĀ® Xe MAX 100 (DG1)", + MfgID: "8086", + DevID: "4909", + }, + { + Model: "DG2", + MfgID: "8086", + DevID: "(4F80|4F81|4F82)", + }, + { + Model: "IntelĀ® Arc ā„¢ A770M Graphics", + MfgID: "8086", + DevID: "5690", + }, + { + Model: "IntelĀ® Arc ā„¢ A730M Graphics (Alchemist)", + MfgID: "8086", + DevID: "5691", + }, + { + Model: "IntelĀ® Arc ā„¢ A550M Graphics (Alchemist)", + MfgID: "8086", + DevID: "5692", + }, + { + Model: "IntelĀ® Arc ā„¢ A370M Graphics (Alchemist)", + MfgID: "8086", + DevID: "5693", + }, + { + Model: "IntelĀ® Arc ā„¢ A350M Graphics (Alchemist)", + MfgID: "8086", + DevID: "5694", + }, + { + Model: "IntelĀ® Arc ā„¢ A770 Graphics", + MfgID: "8086", + DevID: "56A0", + }, + { + Model: "IntelĀ® Arc ā„¢ A750 Graphics (Alchemist)", + MfgID: "8086", + DevID: "56A1", + }, + { + Model: "IntelĀ® Arc ā„¢ A380 Graphics (Alchemist)", + MfgID: "8086", + DevID: "56A5", + }, + { + Model: "IntelĀ® Arc ā„¢ A310 Graphics (Alchemist)", + MfgID: "8086", + DevID: "56A6", + }, + { + Model: "IntelĀ® Data Center GPU Flex 170", + MfgID: "8086", + DevID: "56C0", + }, + { + Model: "IntelĀ® Data Center GPU Flex 140", + MfgID: "8086", + DevID: "56C1", + }, + { + Model: "IntelĀ® Data Center GPU Flex 170V", + MfgID: "8086", + DevID: "56C2", + }, +} diff --git a/internal/report/html.go b/internal/report/html.go new file mode 100644 index 0000000..a90f80a --- /dev/null +++ b/internal/report/html.go @@ -0,0 +1,1043 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "bytes" + "fmt" + "html" + "log/slog" + "math" + "sort" + "strconv" + "strings" + texttemplate "text/template" + + "golang.org/x/exp/rand" +) + +func getHtmlReportBegin() string { + var sb strings.Builder + sb.WriteString(` +`) + sb.WriteString(` + +`) + sb.WriteString("\n") + sb.WriteString(` + Intel® PerfSpect + + +`) + // link the style sheets and javascript + sb.WriteString(` + + + + + + + + `) + // add content class style + sb.WriteString(` + +`) + // add sidebar class styles + sb.WriteString(` + + `) + sb.WriteString("\n") + + return sb.String() +} + +func getHtmlReportMenu(allTableValues []TableValues) string { + var sb strings.Builder + // if none of the tables have menu labels, don't add the sidebar + hasMenuLabels := false + for _, tableValues := range allTableValues { + if tableValues.MenuLabel != "" { + hasMenuLabels = true + break + } + } + if hasMenuLabels { + sb.WriteString("
\n") + sb.WriteString("CONTENTS\n") + sb.WriteString("<\n") + // insert menu items into sidebar + for _, tableValues := range allTableValues { + if tableValues.MenuLabel != "" { + sb.WriteString(fmt.Sprintf("%s\n", html.EscapeString(tableValues.Name), html.EscapeString(tableValues.MenuLabel))) + } + } + sb.WriteString("
\n") // end of sidebar + } + return sb.String() +} + +func getHtmlReportSidebarJavascript() string { + return ` + + ` +} + +func createHtmlReport(allTableValues []TableValues, targetName string) (out []byte, err error) { + var sb strings.Builder + sb.WriteString(getHtmlReportBegin()) + + // body starts here + sb.WriteString("\n") + sb.WriteString("
\n") + // add the sidebar/menu + sb.WriteString(getHtmlReportMenu(allTableValues)) + // add the tables + sb.WriteString("
\n") + sb.WriteString("

Intel® PerfSpect

\n") + sb.WriteString(` + +`) + for _, tableValues := range allTableValues { + // print the table name + sb.WriteString(fmt.Sprintf("

%[1]s

\n", html.EscapeString(tableValues.Name))) + // if there's no data in the table, print a message and continue + if len(tableValues.Fields) == 0 || len(tableValues.Fields[0].Values) == 0 { + sb.WriteString("

" + noDataFound + "

\n") + continue + } + // render the tables + if tableValues.HTMLTableRendererFunc != nil { // custom table renderer + sb.WriteString(tableValues.HTMLTableRendererFunc(tableValues, targetName)) + } else { + sb.WriteString(DefaultHTMLTableRendererFunc(tableValues)) + } + } + sb.WriteString("
\n") // end of myTables + sb.WriteString("
\n") + + // add the sidebar toggle function + sb.WriteString(getHtmlReportSidebarJavascript()) + + sb.WriteString("\n") + sb.WriteString("\n") + out = []byte(sb.String()) + return +} + +func createHtmlReportMultiTarget(allTargetsTableValues [][]TableValues, targetNames []string) (out []byte, err error) { + var sb strings.Builder + sb.WriteString(getHtmlReportBegin()) + + // body starts here + sb.WriteString("\n") + sb.WriteString("
\n") + // add the sidebar/menu + sb.WriteString(getHtmlReportMenu(allTargetsTableValues[0])) + // add the tables + sb.WriteString("
\n") + sb.WriteString("

Intel® PerfSpect

\n") + sb.WriteString(` + +`) + for tableIndex := 0; tableIndex < len(allTargetsTableValues[0]); tableIndex++ { + oneTableValuesForAllTargets := []TableValues{} + for targetIndex, allTableValues := range allTargetsTableValues { + if allTableValues[tableIndex].HasRows && allTableValues[tableIndex].HTMLMultiTargetTableRendererFunc == nil { + // print the table name only one time per table + if targetIndex == 0 { + sb.WriteString(fmt.Sprintf("

%[1]s

\n", html.EscapeString(allTableValues[tableIndex].Name))) + } + // print the target name + sb.WriteString(fmt.Sprintf("

%s

\n", targetNames[targetIndex])) + // if there's no data in the table, print a message and continue + if len(allTableValues[tableIndex].Fields) == 0 || len(allTableValues[tableIndex].Fields[0].Values) == 0 { + sb.WriteString("

" + noDataFound + "

\n") + continue + } + if allTableValues[tableIndex].HTMLTableRendererFunc != nil { // custom table renderer + sb.WriteString(allTableValues[tableIndex].HTMLTableRendererFunc(allTableValues[tableIndex], targetNames[targetIndex])) + } else { + sb.WriteString(DefaultHTMLTableRendererFunc(allTableValues[tableIndex])) + } + } else { + oneTableValuesForAllTargets = append(oneTableValuesForAllTargets, allTableValues[tableIndex]) + } + } + if len(oneTableValuesForAllTargets) > 0 { + // print the table name + sb.WriteString(fmt.Sprintf("

%[1]s

\n", html.EscapeString(oneTableValuesForAllTargets[0].Name))) + if allTargetsTableValues[0][tableIndex].HTMLMultiTargetTableRendererFunc != nil { + sb.WriteString(allTargetsTableValues[0][tableIndex].HTMLMultiTargetTableRendererFunc(oneTableValuesForAllTargets, targetNames)) + } else { + // render the multi-target table + sb.WriteString(RenderMultiTargetTableValuesAsHTML(oneTableValuesForAllTargets, targetNames)) + } + } + } + sb.WriteString("
\n") // end of myTables + sb.WriteString("
\n") + + // add the sidebar toggle function + sb.WriteString(getHtmlReportSidebarJavascript()) + + sb.WriteString("\n") + sb.WriteString("\n") + out = []byte(sb.String()) + return +} + +const datasetTemplate = ` +{ + label: '{{.Label}}', + data: [{{.Data}}], + backgroundColor: '{{.Color}}', + borderColor: '{{.Color}}', + borderWidth: 1, + showLine: true +} +` +const scatterChartTemplate = `
+ +
+ +` + +type scatterChartTemplateStruct struct { + ID string + Datasets string + XaxisText string + YaxisText string + TitleText string + DisplayTitle string + DisplayLegend string + AspectRatio string + SuggestedMin string + SuggestedMax string +} + +func renderHTMLTable(tableHeaders []string, tableValues [][]string, class string, valuesStyle [][]string) string { + var sb strings.Builder + sb.WriteString(``) + if len(tableHeaders) > 0 { + sb.WriteString(``) + sb.WriteString(``) + for _, label := range tableHeaders { + sb.WriteString(``) + } + sb.WriteString(``) + sb.WriteString(``) + } + sb.WriteString(``) + for rowIdx, rowValues := range tableValues { + sb.WriteString(``) + for colIdx, value := range rowValues { + var style string + if len(valuesStyle) > rowIdx && len(valuesStyle[rowIdx]) > colIdx { + style = ` style="` + valuesStyle[rowIdx][colIdx] + `"` + } + sb.WriteString(`` + value + ``) + } + sb.WriteString(``) + } + sb.WriteString(``) + sb.WriteString(`
` + label + `
`) + return sb.String() +} + +func DefaultHTMLTableRendererFunc(tableValues TableValues) string { + if tableValues.HasRows { // print the field names as column headings across the top of the table + headers := []string{} + for _, field := range tableValues.Fields { + headers = append(headers, field.Name) + } + values := [][]string{} + for row := 0; row < len(tableValues.Fields[0].Values); row++ { + rowValues := []string{} + for _, field := range tableValues.Fields { + rowValues = append(rowValues, field.Values[row]) + } + values = append(values, rowValues) + } + return renderHTMLTable(headers, values, "pure-table pure-table-striped", [][]string{}) + } else { // print the field name followed by its value + values := [][]string{} + var tableValueStyles [][]string + for _, field := range tableValues.Fields { + rowValues := []string{} + rowValues = append(rowValues, field.Name) + rowValues = append(rowValues, field.Values[0]) + values = append(values, rowValues) + tableValueStyles = append(tableValueStyles, []string{"font-weight:bold"}) + } + return renderHTMLTable([]string{}, values, "pure-table pure-table-striped", tableValueStyles) + } +} + +// RenderMultiTargetTableValuesAsHTML renders a table for multiple targets +// tableValues is a slice of TableValues, each of which represents the same table from a single target +func RenderMultiTargetTableValuesAsHTML(tableValues []TableValues, targetNames []string) string { + values := [][]string{} + var tableValueStyles [][]string + for fieldIndex, field := range tableValues[0].Fields { + rowValues := []string{} + rowValues = append(rowValues, field.Name) + for _, targetTableValues := range tableValues { + if len(targetTableValues.Fields) > fieldIndex && len(targetTableValues.Fields[fieldIndex].Values) > 0 { + rowValues = append(rowValues, targetTableValues.Fields[fieldIndex].Values[0]) + } else { + rowValues = append(rowValues, "") + } + } + values = append(values, rowValues) + tableValueStyles = append(tableValueStyles, []string{"font-weight:bold"}) + } + headers := []string{""} + headers = append(headers, targetNames...) + return renderHTMLTable(headers, values, "pure-table pure-table-striped", tableValueStyles) +} + +func dimmDetails(dimm []string) (details string) { + if strings.Contains(dimm[SizeIdx], "No") { + details = "No Module Installed" + } else { + // Intel PMEM modules may have serial number appended to end of part number... + // strip that off so it doesn't mess with color selection later + partNumber := dimm[PartIdx] + if strings.Contains(dimm[DetailIdx], "Synchronous Non-Volatile") && + dimm[ManufacturerIdx] == "Intel" && + strings.HasSuffix(dimm[PartIdx], dimm[SerialIdx]) { + partNumber = dimm[PartIdx][:len(dimm[PartIdx])-len(dimm[SerialIdx])] + } + details = dimm[SizeIdx] + " @" + dimm[ConfiguredSpeedIdx] + details += " " + dimm[TypeIdx] + " " + dimm[DetailIdx] + details += " " + dimm[ManufacturerIdx] + " " + partNumber + } + return +} + +func dimmTableHTMLRenderer(tableValues TableValues, targetName string) string { + if tableValues.Fields[DerivedSocketIdx].Values[0] == "" || tableValues.Fields[DerivedChannelIdx].Values[0] == "" || tableValues.Fields[DerivedSlotIdx].Values[0] == "" { + return DefaultHTMLTableRendererFunc(tableValues) + } + htmlColors := []string{"lightgreen", "orange", "aqua", "lime", "yellow", "beige", "magenta", "violet", "salmon", "pink"} + var slotColorIndices = make(map[string]int) + // socket -> channel -> slot -> dimm details + var dimms = map[string]map[string]map[string]string{} + for dimmIdx := 0; dimmIdx < len(tableValues.Fields[DerivedSocketIdx].Values); dimmIdx++ { + if _, ok := dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]]; !ok { + dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]] = make(map[string]map[string]string) + } + if _, ok := dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[DerivedChannelIdx].Values[dimmIdx]]; !ok { + dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[DerivedChannelIdx].Values[dimmIdx]] = make(map[string]string) + } + dimmValues := []string{} + for _, field := range tableValues.Fields { + dimmValues = append(dimmValues, field.Values[dimmIdx]) + } + dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[DerivedChannelIdx].Values[dimmIdx]][tableValues.Fields[DerivedSlotIdx].Values[dimmIdx]] = dimmDetails(dimmValues) + } + + var socketTableHeaders = []string{"Socket", ""} + var socketTableValues [][]string + var socketKeys []string + for k := range dimms { + socketKeys = append(socketKeys, k) + } + sort.Strings(socketKeys) + for _, socket := range socketKeys { + socketMap := dimms[socket] + socketTableValues = append(socketTableValues, []string{}) + var channelTableHeaders = []string{"Channel", "Slots"} + var channelTableValues [][]string + var channelKeys []int + for k := range socketMap { + channel, err := strconv.Atoi(k) + if err != nil { + slog.Error("failed to convert channel to int", slog.String("error", err.Error())) + return "" + } + channelKeys = append(channelKeys, channel) + } + sort.Ints(channelKeys) + for _, channel := range channelKeys { + channelMap := socketMap[strconv.Itoa(channel)] + channelTableValues = append(channelTableValues, []string{}) + var slotTableHeaders []string + var slotTableValues [][]string + var slotTableValuesStyles [][]string + var slotKeys []string + for k := range channelMap { + slotKeys = append(slotKeys, k) + } + sort.Strings(slotKeys) + slotTableValues = append(slotTableValues, []string{}) + slotTableValuesStyles = append(slotTableValuesStyles, []string{}) + for _, slot := range slotKeys { + dimmDetails := channelMap[slot] + slotTableValues[0] = append(slotTableValues[0], dimmDetails) + var slotColor string + if dimmDetails == "No Module Installed" { + slotColor = "background-color:silver" + } else { + if _, ok := slotColorIndices[dimmDetails]; !ok { + slotColorIndices[dimmDetails] = int(math.Min(float64(len(slotColorIndices)), float64(len(htmlColors)-1))) + } + slotColor = "background-color:" + htmlColors[slotColorIndices[dimmDetails]] + } + slotTableValuesStyles[0] = append(slotTableValuesStyles[0], slotColor) + } + slotTable := renderHTMLTable(slotTableHeaders, slotTableValues, "pure-table pure-table-bordered", slotTableValuesStyles) + // channel number + channelTableValues[len(channelTableValues)-1] = append(channelTableValues[len(channelTableValues)-1], strconv.Itoa(channel)) + // slot table + channelTableValues[len(channelTableValues)-1] = append(channelTableValues[len(channelTableValues)-1], slotTable) + // style + } + channelTable := renderHTMLTable(channelTableHeaders, channelTableValues, "pure-table pure-table-bordered", [][]string{}) + // socket number + socketTableValues[len(socketTableValues)-1] = append(socketTableValues[len(socketTableValues)-1], socket) + // channel table + socketTableValues[len(socketTableValues)-1] = append(socketTableValues[len(socketTableValues)-1], channelTable) + } + return renderHTMLTable(socketTableHeaders, socketTableValues, "pure-table pure-table-bordered", [][]string{}) +} + +type scatterPoint struct { + x float64 + y float64 +} + +func renderScatterChart(data [][]scatterPoint, datasetNames []string, config scatterChartTemplateStruct) string { + allFormattedPoints := []string{} + for dataIdx := 0; dataIdx < len(data); dataIdx++ { + formattedPoints := []string{} + for _, point := range data[dataIdx] { + formattedPoints = append(formattedPoints, fmt.Sprintf("{x: %f, y: %f}", point.x, point.y)) + } + allFormattedPoints = append(allFormattedPoints, strings.Join(formattedPoints, ",")) + } + datasets := []string{} + for dataIdx, formattedPoints := range allFormattedPoints { + specValues := formattedPoints + dst := texttemplate.Must(texttemplate.New("datasetTemplate").Parse(datasetTemplate)) + buf := new(bytes.Buffer) + err := dst.Execute(buf, struct { + Label string + Data string + Color string + }{ + Label: datasetNames[dataIdx], + Data: specValues, + Color: getColor(dataIdx), + }) + if err != nil { + slog.Error("error executing template", slog.String("error", err.Error())) + return "Error rendering chart." + } + datasets = append(datasets, buf.String()) + } + sct := texttemplate.Must(texttemplate.New("scatterChartTemplate").Parse(scatterChartTemplate)) + buf := new(bytes.Buffer) + config.Datasets = strings.Join(datasets, ",") + err := sct.Execute(buf, config) + if err != nil { + slog.Error("error executing template", slog.String("error", err.Error())) + return "Error rendering chart." + } + out := buf.String() + out += "\n" + return out +} + +func renderFrequencyTable(tableValues TableValues) (out string) { + var rows [][]string + headers := []string{""} + valuesStyles := [][]string{} + for i := 0; i < len(tableValues.Fields[0].Values); i++ { + headers = append(headers, fmt.Sprintf("%d", i+1)) + } + for _, field := range tableValues.Fields[1:] { + row := append([]string{field.Name}, field.Values...) + rows = append(rows, row) + valuesStyles = append(valuesStyles, []string{"font-weight:bold"}) + } + out = renderHTMLTable(headers, rows, "pure-table pure-table-striped", valuesStyles) + return +} + +func coreTurboFrequencyTableHTMLRenderer(tableValues TableValues, targetName string) string { + data := [][]scatterPoint{} + datasetNames := []string{} + for _, field := range tableValues.Fields[1:] { + points := []scatterPoint{} + for i, val := range field.Values { + if val == "" { + break + } + freq, err := strconv.ParseFloat(val, 64) + if err != nil { + slog.Error("error parsing frequency", slog.String("error", err.Error())) + return "" + } + points = append(points, scatterPoint{float64(i + 1), freq}) + } + if len(points) > 0 { + data = append(data, points) + datasetNames = append(datasetNames, field.Name) + } + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("turboFrequency%d", rand.Intn(10000)), + XaxisText: "Core Count", + YaxisText: "Frequency (GHz)", + TitleText: "", + DisplayTitle: "false", + DisplayLegend: "true", + AspectRatio: "4", + SuggestedMin: "2", + SuggestedMax: "4", + } + out := renderScatterChart(data, datasetNames, chartConfig) + out += "\n" + out += renderFrequencyTable(tableValues) + return out +} + +func cpuFrequencyTableHtmlRenderer(tableValues TableValues, targetName string) string { + return coreTurboFrequencyTableHTMLRenderer(tableValues, targetName) +} + +func memoryLatencyTableHtmlRenderer(tableValues TableValues, targetName string) string { + return memoryLatencyTableMultiTargetHtmlRenderer([]TableValues{tableValues}, []string{targetName}) +} + +func memoryLatencyTableMultiTargetHtmlRenderer(allTableValues []TableValues, targetNames []string) string { + data := [][]scatterPoint{} + datasetNames := []string{} + for targetIdx, tableValues := range allTableValues { + points := []scatterPoint{} + for valIdx := range tableValues.Fields[0].Values { + latency, err := strconv.ParseFloat(tableValues.Fields[0].Values[valIdx], 64) + if err != nil { + slog.Error("error parsing latency", slog.String("error", err.Error())) + return "" + } + bandwidth, err := strconv.ParseFloat(tableValues.Fields[1].Values[valIdx], 64) + if err != nil { + slog.Error("error parsing bandwidth", slog.String("error", err.Error())) + return "" + } + points = append(points, scatterPoint{bandwidth, latency}) + } + data = append(data, points) + datasetNames = append(datasetNames, targetNames[targetIdx]) + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("latencyBandwidth%d", rand.Intn(10000)), + XaxisText: "Bandwidth (MB/s)", + YaxisText: "Latency (ns)", + TitleText: "", + DisplayTitle: "false", + DisplayLegend: "true", + AspectRatio: "4", + SuggestedMin: "0", + SuggestedMax: "0", + } + return renderScatterChart(data, datasetNames, chartConfig) +} + +func getColor(idx int) string { + // color-blind safe palette from here: http://mkweb.bcgsc.ca/colorblind/palettes.mhtml#page-container + colors := []string{"#9F0162", "#009F81", "#FF5AAF", "#00FCCF", "#8400CD", "#008DF9", "#00C2F9", "#FFB2FD", "#A40122", "#E20134", "#FF6E3A", "#FFC33B"} + return colors[idx%len(colors)] +} + +func cpuUtilizationTableHTMLRenderer(tableValues TableValues, targetName string) string { + data := [][]scatterPoint{} + datasetNames := []string{} + // collect the busy (100 - idle) values for each CPU + cpuBusyStats := make(map[int][]float64) + idleFieldIdx := len(tableValues.Fields) - 1 + cpuFieldIdx := 1 + for i := range tableValues.Fields[0].Values { + idle, err := strconv.ParseFloat(tableValues.Fields[idleFieldIdx].Values[i], 64) + if err != nil { + continue + } + busy := 100 - idle + cpu, err := strconv.Atoi(tableValues.Fields[cpuFieldIdx].Values[i]) + if err != nil { + continue + } + if _, ok := cpuBusyStats[cpu]; !ok { + cpuBusyStats[cpu] = []float64{} + } + cpuBusyStats[cpu] = append(cpuBusyStats[cpu], busy) + } + // sort map keys by cpu number + var keys []int + for cpu := range cpuBusyStats { + keys = append(keys, cpu) + } + sort.Ints(keys) + // build the data + for _, cpu := range keys { + points := []scatterPoint{} + for i, busy := range cpuBusyStats[cpu] { + points = append(points, scatterPoint{float64(i), busy}) + } + if len(points) > 0 { + data = append(data, points) + datasetNames = append(datasetNames, fmt.Sprintf("CPU %d", cpu)) + } + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("cpuUtilization%d", rand.Intn(10000)), + XaxisText: "Time/Samples", + YaxisText: "% Utilization", + TitleText: "", + DisplayTitle: "false", + DisplayLegend: "false", + AspectRatio: "2", + SuggestedMin: "0", + SuggestedMax: "100", + } + return renderScatterChart(data, datasetNames, chartConfig) +} + +func averageCPUUtilizationTableHTMLRenderer(tableValues TableValues, targetName string) string { + data := [][]scatterPoint{} + datasetNames := []string{} + for _, field := range tableValues.Fields[1:] { + points := []scatterPoint{} + for i, val := range field.Values { + if val == "" { + break + } + util, err := strconv.ParseFloat(val, 64) + if err != nil { + slog.Error("error parsing percentage", slog.String("error", err.Error())) + return "" + } + points = append(points, scatterPoint{float64(i), util}) + } + if len(points) > 0 { + data = append(data, points) + datasetNames = append(datasetNames, field.Name) + } + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("avgCPUUtil%d", rand.Intn(10000)), + XaxisText: "Time/Samples", + YaxisText: "% Utilization", + TitleText: "", + DisplayTitle: "false", + DisplayLegend: "true", + AspectRatio: "2", + SuggestedMin: "0", + SuggestedMax: "100", + } + return renderScatterChart(data, datasetNames, chartConfig) +} + +func irqRateTableHTMLRenderer(tableValues TableValues, targetName string) string { + data := [][]scatterPoint{} + datasetNames := []string{} + + for _, field := range tableValues.Fields[2:] { // 1 data set per field, e.g., %usr, %nice, etc., skip Time and CPU fields + datasetNames = append(datasetNames, field.Name) + // sum the values in the field per timestamp, store the sum as a point + timeStamp := tableValues.Fields[0].Values[0] + points := []scatterPoint{} + total := 0.0 + for i := range field.Values { + if tableValues.Fields[0].Values[i] != timeStamp { // new timestamp? + points = append(points, scatterPoint{float64(len(points)), total}) + total = 0.0 + timeStamp = tableValues.Fields[0].Values[i] + } + val, err := strconv.ParseFloat(field.Values[i], 64) + if err != nil { + slog.Error("error parsing value", slog.String("error", err.Error())) + return "" + } + total += val + } + points = append(points, scatterPoint{float64(len(points)), total}) // add the point for the last timestamp + // save the points in the data slice + data = append(data, points) + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("irqRate%d", rand.Intn(10000)), + XaxisText: "Time/Samples", + YaxisText: "IRQ/s", + TitleText: "", + DisplayTitle: "false", + DisplayLegend: "true", + AspectRatio: "2", + SuggestedMin: "0", + SuggestedMax: "0", + } + return renderScatterChart(data, datasetNames, chartConfig) +} + +// driveStatsTableHTMLRenderer renders charts of drive statistics +// - one scatter chart per drive, showing the drive's utilization over time +// - each drive stat is a separate dataset within the chart +func driveStatsTableHTMLRenderer(tableValues TableValues, targetName string) string { + var out string + driveStats := make(map[string][][]string) + for i := 0; i < len(tableValues.Fields[0].Values); i++ { + drive := tableValues.Fields[1].Values[i] + if _, ok := driveStats[drive]; !ok { + driveStats[drive] = make([][]string, len(tableValues.Fields)-2) + } + for j := 0; j < len(tableValues.Fields)-2; j++ { + driveStats[drive][j] = append(driveStats[drive][j], tableValues.Fields[j+2].Values[i]) + } + } + var keys []string + for drive := range driveStats { + keys = append(keys, drive) + } + sort.Strings(keys) + for _, drive := range keys { + data := [][]scatterPoint{} + datasetNames := []string{} + for i, statVals := range driveStats[drive] { + points := []scatterPoint{} + for i, val := range statVals { + if val == "" { + slog.Error("empty stat value", slog.String("drive", drive), slog.Int("index", i)) + return "" + } + util, err := strconv.ParseFloat(val, 64) + if err != nil { + slog.Error("error parsing stat", slog.String("error", err.Error())) + return "" + } + points = append(points, scatterPoint{float64(i), util}) + } + if len(points) > 0 { + data = append(data, points) + datasetNames = append(datasetNames, tableValues.Fields[i+2].Name) + } + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("driveStats%d", rand.Intn(10000)), + XaxisText: "Time/Samples", + YaxisText: "", + TitleText: drive, + DisplayTitle: "true", + DisplayLegend: "true", + AspectRatio: "2", + SuggestedMin: "0", + SuggestedMax: "0", + } + out += renderScatterChart(data, datasetNames, chartConfig) + } + return out +} + +// networkStatsTableHTMLRenderer renders charts of network device statistics +// - one scatter chart per network device, showing the device's utilization over time +// - each network stat is a separate dataset within the chart +func networkStatsTableHTMLRenderer(tableValues TableValues, targetName string) string { + var out string + nicStats := make(map[string][][]string) + for i := 0; i < len(tableValues.Fields[0].Values); i++ { + drive := tableValues.Fields[1].Values[i] + if _, ok := nicStats[drive]; !ok { + nicStats[drive] = make([][]string, len(tableValues.Fields)-2) + } + for j := 0; j < len(tableValues.Fields)-2; j++ { + nicStats[drive][j] = append(nicStats[drive][j], tableValues.Fields[j+2].Values[i]) + } + } + var keys []string + for drive := range nicStats { + keys = append(keys, drive) + } + sort.Strings(keys) + for _, nic := range keys { + data := [][]scatterPoint{} + datasetNames := []string{} + for i, statVals := range nicStats[nic] { + points := []scatterPoint{} + for i, val := range statVals { + if val == "" { + slog.Error("empty stat value", slog.String("nic", nic), slog.Int("index", i)) + return "" + } + util, err := strconv.ParseFloat(val, 64) + if err != nil { + slog.Error("error parsing stat", slog.String("error", err.Error())) + return "" + } + points = append(points, scatterPoint{float64(i), util}) + } + if len(points) > 0 { + data = append(data, points) + datasetNames = append(datasetNames, tableValues.Fields[i+2].Name) + } + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("nicStats%d", rand.Intn(10000)), + XaxisText: "Time/Samples", + YaxisText: "", + TitleText: nic, + DisplayTitle: "true", + DisplayLegend: "true", + AspectRatio: "2", + SuggestedMin: "0", + SuggestedMax: "0", + } + out += renderScatterChart(data, datasetNames, chartConfig) + } + return out +} + +func memoryStatsTableHTMLRenderer(tableValues TableValues, targetName string) string { + data := [][]scatterPoint{} + datasetNames := []string{} + for _, field := range tableValues.Fields[1:] { + points := []scatterPoint{} + for i, val := range field.Values { + if val == "" { + break + } + stat, err := strconv.ParseFloat(val, 64) + if err != nil { + slog.Error("error parsing stat", slog.String("error", err.Error())) + return "" + } + points = append(points, scatterPoint{float64(i), stat}) + } + if len(points) > 0 { + data = append(data, points) + datasetNames = append(datasetNames, field.Name) + } + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("memoryStats%d", rand.Intn(10000)), + XaxisText: "Time/Samples", + YaxisText: "kilobytes", + TitleText: "", + DisplayTitle: "false", + DisplayLegend: "true", + AspectRatio: "2", + SuggestedMin: "0", + SuggestedMax: "0", + } + return renderScatterChart(data, datasetNames, chartConfig) +} + +func powerStatsTableHTMLRenderer(tableValues TableValues, targetName string) string { + data := [][]scatterPoint{} + datasetNames := []string{} + for _, field := range tableValues.Fields[1:] { + points := []scatterPoint{} + for i, val := range field.Values { + if val == "" { + break + } + stat, err := strconv.ParseFloat(val, 64) + if err != nil { + slog.Error("error parsing stat", slog.String("error", err.Error())) + return "" + } + points = append(points, scatterPoint{float64(i), stat}) + } + if len(points) > 0 { + data = append(data, points) + datasetNames = append(datasetNames, field.Name) + } + } + chartConfig := scatterChartTemplateStruct{ + ID: fmt.Sprintf("powerStats%d", rand.Intn(10000)), + XaxisText: "Time/Samples", + YaxisText: "Watts", + TitleText: "", + DisplayTitle: "false", + DisplayLegend: "true", + AspectRatio: "2", + SuggestedMin: "0", + SuggestedMax: "0", + } + return renderScatterChart(data, datasetNames, chartConfig) +} + +func codePathFrequencyTableHTMLRenderer(tableValues TableValues, targetName string) string { + out := ` +` + out += renderFlameGraph("System", tableValues, "System Paths") + out += renderFlameGraph("Java", tableValues, "Java Paths") + return out +} diff --git a/internal/report/html_flamegraph.go b/internal/report/html_flamegraph.go new file mode 100644 index 0000000..02fb140 --- /dev/null +++ b/internal/report/html_flamegraph.go @@ -0,0 +1,187 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "log" + "strconv" + "strings" + texttemplate "text/template" + + "golang.org/x/exp/rand" +) + +const flameGraphTemplate = ` +
+
+ +

{{.Header}}

+
+
+
+
+
+ +` + +type flameGraphTemplateStruct struct { + ID string + Data string + Header string +} + +// Folded data conversion adapted from https://github.com/spiermar/burn +// Copyright Ā© 2017 Martin Spier +// Apache License, Version 2.0 +func reverse(strings []string) { + for i, j := 0, len(strings)-1; i < j; i, j = i+1, j-1 { + strings[i], strings[j] = strings[j], strings[i] + } +} + +type Node struct { + Name string + Value int + Children map[string]*Node +} + +func (n *Node) Add(stackPtr *[]string, index int, value int) { + n.Value += value + if index >= 0 { + head := (*stackPtr)[index] + childPtr, ok := n.Children[head] + if !ok { + childPtr = &(Node{head, 0, make(map[string]*Node)}) + n.Children[head] = childPtr + } + childPtr.Add(stackPtr, index-1, value) + } +} + +func (n *Node) MarshalJSON() ([]byte, error) { + v := make([]Node, 0, len(n.Children)) + for _, value := range n.Children { + v = append(v, *value) + } + + return json.Marshal(&struct { + Name string `json:"name"` + Value int `json:"value"` + Children []Node `json:"children"` + }{ + Name: n.Name, + Value: n.Value, + Children: v, + }) +} + +var maxStackDepth = 50 + +func convertFoldedToJSON(folded string) (out string, err error) { + rootNode := Node{Name: "root", Value: 0, Children: make(map[string]*Node)} + scanner := bufio.NewScanner(strings.NewReader(folded)) + for scanner.Scan() { + line := scanner.Text() + sep := strings.LastIndex(line, " ") + s := line[:sep] + v := line[sep+1:] + stack := strings.Split(s, ";") + reverse(stack) + if len(stack) > maxStackDepth { + log.Printf("Trimming call stack depth from %d to %d", len(stack), maxStackDepth) + stack = stack[:maxStackDepth] + } + var i int + i, err = strconv.Atoi(v) + if err != nil { + return + } + rootNode.Add(&stack, len(stack)-1, i) + } + outbytes, err := rootNode.MarshalJSON() + out = string(outbytes) + return +} + +func renderFlameGraph(header string, tableValues TableValues, field string) (out string) { + fieldIdx, err := getFieldIndex(field, tableValues) + if err != nil { + log.Panicf("didn't find expected field (%s) in table: %v", field, err) + } + folded := tableValues.Fields[fieldIdx].Values[0] + if folded == "" { + out += `

` + header + `

` + out += noDataFound + return + } + jsonStacks, err := convertFoldedToJSON(folded) + if err != nil { + log.Printf("failed to convert folded data: %v", err) + out += "Error." + return + } + fg := texttemplate.Must(texttemplate.New("flameGraphTemplate").Parse(flameGraphTemplate)) + buf := new(bytes.Buffer) + err = fg.Execute(buf, flameGraphTemplateStruct{ + ID: fmt.Sprintf("%d%s", rand.Intn(10000), header), + Data: jsonStacks, + Header: header, + }) + if err != nil { + log.Printf("failed to render flame graph template: %v", err) + out += "Error." + return + } + out += buf.String() + out += "\n" + return +} diff --git a/internal/report/report.go b/internal/report/report.go new file mode 100644 index 0000000..17ce6cc --- /dev/null +++ b/internal/report/report.go @@ -0,0 +1,550 @@ +// Package report provides functions to generate reports in various formats such as txt, json, html, xlsx. +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "log/slog" + "os" + "strconv" + "strings" + + "perfspect/internal/script" + + "github.com/xuri/excelize/v2" +) + +const ( + FormatHtml = "html" + FormatXlsx = "xlsx" + FormatJson = "json" + FormatTxt = "txt" + FormatRaw = "raw" + FormatAll = "all" +) + +const noDataFound = "No data found." + +var FormatOptions = []string{FormatHtml, FormatXlsx, FormatJson, FormatTxt} + +// Process processes the given tables and script outputs to generate table values. +// It collects values for each field in the tables and returns a slice of TableValues. +// If any error occurs during processing, it is returned along with the table values. + +func Process(tableNames []string, scriptOutputs map[string]script.ScriptOutput) (allTableValues []TableValues, err error) { + for _, tableName := range tableNames { + allTableValues = append(allTableValues, GetValuesForTable(tableName, scriptOutputs)) + } + return +} + +// Create generates a report in the specified format based on the provided tables, table values, and script outputs. +// The function ensures that all fields have the same number of values before generating the report. +// It supports formats such as txt, json, html, xlsx. +// If the format is not supported, the function panics with an error message. +// +// Parameters: +// - format: The desired format of the report (txt, json, html, xlsx, raw). +// - tableValues: The values for each field in each table. +// - scriptOutputs: The outputs of any scripts used in the report. +// - targetName: The name of the target for which the report is being generated. +// +// Returns: +// - out: The generated report as a byte slice. +// - err: An error, if any occurred during report generation. +func Create(format string, allTableValues []TableValues, scriptOutputs map[string]script.ScriptOutput, targetName string) (out []byte, err error) { + // make sure that all fields have the same number of values + for _, tableValue := range allTableValues { + numRows := -1 + for _, fieldValues := range tableValue.Fields { + if numRows == -1 { + numRows = len(fieldValues.Values) + continue + } + if len(fieldValues.Values) != numRows { + return nil, fmt.Errorf("expected %d value(s) for field, found %d", numRows, len(fieldValues.Values)) + } + } + } + // create the report based on the specified format + switch format { + case FormatTxt: + return createTextReport(allTableValues) + case FormatJson: + return createJsonReport(allTableValues) + case FormatHtml: + return createHtmlReport(allTableValues, targetName) + case FormatXlsx: + return createXlsxReport(allTableValues) + } + panic(fmt.Sprintf("expected one of %s, got %s", strings.Join(FormatOptions, ", "), format)) +} + +func CreateMultiTarget(format string, allTargetsTableValues [][]TableValues, targetNames []string) (out []byte, err error) { + switch format { + case "html": + return createHtmlReportMultiTarget(allTargetsTableValues, targetNames) + case "xlsx": + return createXlsxReportMultiTarget(allTargetsTableValues, targetNames) + } + panic("only HTML and XLSX multi-target report supported currently") +} + +func createTextReport(allTableValues []TableValues) (out []byte, err error) { + var sb strings.Builder + for _, tableValues := range allTableValues { + sb.WriteString(fmt.Sprintf("%s\n", tableValues.Name)) + for i := 0; i < len(tableValues.Name); i++ { + sb.WriteString("=") + } + sb.WriteString("\n") + if len(tableValues.Fields) == 0 || len(tableValues.Fields[0].Values) == 0 { + sb.WriteString(noDataFound + "\n\n") + continue + } + // custom renderer defined? + if tableValues.TextTableRendererFunc != nil { + sb.WriteString(tableValues.TextTableRendererFunc(tableValues)) + } else { + sb.WriteString(DefaultTextTableRendererFunc(tableValues)) + } + sb.WriteString("\n") + } + out = []byte(sb.String()) + return +} + +func DefaultTextTableRendererFunc(tableValues TableValues) string { + var sb strings.Builder + if tableValues.HasRows { // print the field names as column headings across the top of the table + // find the longest item per column -- can be the field name (column header) or a value + maxFieldLen := make(map[string]int) + for i, field := range tableValues.Fields { + // the last column shouldn't occupy more space than the value + if i == len(tableValues.Fields)-1 { + maxFieldLen[field.Name] = 0 + continue + } + // other columns should occupy the larger of the field name or the longest value + maxFieldLen[field.Name] = len(field.Name) + for _, val := range field.Values { + if len(val) > maxFieldLen[field.Name] { + maxFieldLen[field.Name] = len(val) + } + } + } + columnSpacing := 3 + // print the field names + for _, field := range tableValues.Fields { + sb.WriteString(fmt.Sprintf("%-*s", maxFieldLen[field.Name]+columnSpacing, field.Name)) + } + sb.WriteString("\n") + // underline the field names + for _, field := range tableValues.Fields { + underline := "" + for i := 0; i < len(field.Name); i++ { + underline += "-" + } + sb.WriteString(fmt.Sprintf("%-*s", maxFieldLen[field.Name]+columnSpacing, underline)) + } + sb.WriteString("\n") + // print the rows + numRows := len(tableValues.Fields[0].Values) + for row := 0; row < numRows; row++ { + for fieldIdx, field := range tableValues.Fields { + sb.WriteString(fmt.Sprintf("%-*s", maxFieldLen[field.Name]+columnSpacing, tableValues.Fields[fieldIdx].Values[row])) + } + sb.WriteString("\n") + } + } else { + // get the longest field name to format the table nicely + maxFieldNameLen := 0 + for _, field := range tableValues.Fields { + if len(field.Name) > maxFieldNameLen { + maxFieldNameLen = len(field.Name) + } + } + // print the field names followed by their value + for _, field := range tableValues.Fields { + var value string + if len(field.Values) > 0 { + value = field.Values[0] + } + sb.WriteString(fmt.Sprintf("%s%-*s %s\n", field.Name, maxFieldNameLen-len(field.Name)+1, ":", value)) + } + } + return sb.String() +} + +func createJsonReport(allTableValues []TableValues) (out []byte, err error) { + type outRecord map[string]string + type outTable []outRecord + type outReport map[string]outTable + oReport := make(outReport) + for _, tableValues := range allTableValues { + var oTable outTable + if len(tableValues.Fields) == 0 { + oReport[tableValues.Name] = oTable + continue + } + numRecords := len(tableValues.Fields[0].Values) + if numRecords > 0 { + for recordIdx := 0; recordIdx < numRecords; recordIdx++ { + oRecord := make(outRecord) + for _, field := range tableValues.Fields { + oRecord[field.Name] = field.Values[recordIdx] + } + oTable = append(oTable, oRecord) + } + } else { + // insert an empty record + oRecord := make(outRecord) + for _, field := range tableValues.Fields { + oRecord[field.Name] = "" + } + oTable = append(oTable, oRecord) + } + oReport[tableValues.Name] = oTable + } + return json.MarshalIndent(oReport, "", " ") +} + +func cellName(col int, row int) (name string) { + columnName, err := excelize.ColumnNumberToName(col) + if err != nil { + return + } + name, err = excelize.JoinCellName(columnName, row) + if err != nil { + return + } + return +} + +func renderXlsxTable(tableValues TableValues, f *excelize.File, sheetName string, row *int) { + col := 1 + // print the table name + tableNameStyle, _ := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{ + Bold: true, + }, + }) + f.SetCellValue(sheetName, cellName(col, *row), tableValues.Name) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), tableNameStyle) + *row++ + if len(tableValues.Fields) == 0 || len(tableValues.Fields[0].Values) == 0 { + f.SetCellValue(sheetName, cellName(col, *row), noDataFound) + *row += 2 + return + } + if tableValues.XlsxTableRendererFunc != nil { + tableValues.XlsxTableRendererFunc(tableValues, f, sheetName, row) + } else { + DefaultXlsxTableRendererFunc(tableValues, f, sheetName, row) + } + *row++ +} + +func renderXlsxTableMultiTarget(tableIdx int, allTargetsTableValues [][]TableValues, targetNames []string, f *excelize.File, sheetName string, row *int) { + col := 1 + // print the table name + tableNameStyle, _ := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{ + Bold: true, + }, + }) + targetNameStyle, _ := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{ + Bold: true, + }, + }) + fieldNameStyle, _ := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{ + Bold: true, + }, + }) + + f.SetCellValue(sheetName, cellName(col, *row), allTargetsTableValues[0][tableIdx].Name) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), tableNameStyle) + + if !allTargetsTableValues[0][tableIdx].HasRows { + col += 2 + // print the target names + for _, targetName := range targetNames { + f.SetCellValue(sheetName, cellName(col, *row), targetName) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), targetNameStyle) + col++ + } + *row++ + + // print the field names and values from each target + for fieldIdx, field := range allTargetsTableValues[0][tableIdx].Fields { + col = 2 + f.SetCellValue(sheetName, cellName(col, *row), field.Name) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), fieldNameStyle) + col++ + for targetIdx := 0; targetIdx < len(targetNames); targetIdx++ { + var fieldValue string + if len(allTargetsTableValues[targetIdx][tableIdx].Fields[fieldIdx].Values) > 0 { + fieldValue = allTargetsTableValues[targetIdx][tableIdx].Fields[fieldIdx].Values[0] + } + f.SetCellValue(sheetName, cellName(col, *row), fieldValue) + col++ + } + *row++ + } + } else { + for targetIdx, targetName := range targetNames { + // print the target name + col = 2 + f.SetCellValue(sheetName, cellName(col, *row), targetName) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), targetNameStyle) + *row++ + + // if no data found, print a message and skip to the next target + if len(allTargetsTableValues[targetIdx][tableIdx].Fields) == 0 || len(allTargetsTableValues[targetIdx][tableIdx].Fields[0].Values) == 0 { + f.SetCellValue(sheetName, cellName(col, *row), noDataFound) + *row += 2 + continue + } + + // print the field names as column headings across the top of the table + col = 2 + for _, field := range allTargetsTableValues[targetIdx][tableIdx].Fields { + f.SetCellValue(sheetName, cellName(col, *row), field.Name) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), fieldNameStyle) + col++ + } + *row++ + // print the rows of values + tableRows := len(allTargetsTableValues[targetIdx][tableIdx].Fields[0].Values) + for tableRow := 0; tableRow < tableRows; tableRow++ { + col = 2 + for _, field := range allTargetsTableValues[targetIdx][tableIdx].Fields { + value := getValueForCell(field.Values[tableRow]) + f.SetCellValue(sheetName, cellName(col, *row), value) + col++ + } + *row++ + } + *row++ + } + } + *row++ + +} + +func DefaultXlsxTableRendererFunc(tableValues TableValues, f *excelize.File, sheetName string, row *int) { + headerStyle, _ := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{ + Bold: true, + }, + }) + alignLeft, _ := f.NewStyle(&excelize.Style{ + Alignment: &excelize.Alignment{ + Horizontal: "left", + }, + }) + if tableValues.HasRows { + // print the field names as column headings across the top of the table + col := 2 + for _, field := range tableValues.Fields { + f.SetCellValue(sheetName, cellName(col, *row), field.Name) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), headerStyle) + col++ + } + col = 2 + *row++ + // print the rows + tableRows := len(tableValues.Fields[0].Values) + for tableRow := 0; tableRow < tableRows; tableRow++ { + for _, field := range tableValues.Fields { + value := getValueForCell(field.Values[tableRow]) + f.SetCellValue(sheetName, cellName(col, *row), value) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), alignLeft) + col++ + } + col = 2 + *row++ + } + } else { + // print the field name followed by its value + col := 1 + for _, field := range tableValues.Fields { + var fieldValue string + if len(tableValues.Fields[0].Values) > 0 { + fieldValue = field.Values[0] + } + f.SetCellValue(sheetName, cellName(col, *row), field.Name) + col++ + value := getValueForCell(fieldValue) + f.SetCellValue(sheetName, cellName(col, *row), value) + f.SetCellStyle(sheetName, cellName(col, *row), cellName(col, *row), alignLeft) + col = 1 + *row++ + } + } +} + +const ( + XlsxPrimarySheetName = "Report" + XlsxBriefSheetName = "Brief" +) + +func createXlsxReport(allTableValues []TableValues) (out []byte, err error) { + f := excelize.NewFile() + sheetName := XlsxPrimarySheetName + f.SetSheetName("Sheet1", sheetName) + f.SetColWidth(sheetName, "A", "A", 25) + f.SetColWidth(sheetName, "B", "L", 25) + row := 1 + for _, tableValues := range allTableValues { + if tableValues.Name == SystemSummaryTableName { + row := 1 + sheetName := XlsxBriefSheetName + f.NewSheet(sheetName) + f.SetColWidth(sheetName, "A", "L", 25) + renderXlsxTable(tableValues, f, sheetName, &row) + } else { + renderXlsxTable(tableValues, f, sheetName, &row) + } + } + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + _, err = f.WriteTo(w) + if err != nil { + err = fmt.Errorf("failed to write xlsx report to buffer: %v", err) + return + } + out = buf.Bytes() + return +} + +func createXlsxReportMultiTarget(allTargetsTableValues [][]TableValues, targetNames []string) (out []byte, err error) { + f := excelize.NewFile() + sheetName := XlsxPrimarySheetName + f.SetSheetName("Sheet1", sheetName) + f.SetColWidth(sheetName, "A", "A", 15) + f.SetColWidth(sheetName, "B", "L", 25) + row := 1 + for tableIdx, tableValues := range allTargetsTableValues[0] { + if tableValues.Name == SystemSummaryTableName { + row := 1 + sheetName := XlsxBriefSheetName + f.NewSheet(sheetName) + f.SetColWidth(sheetName, "A", "A", 15) + f.SetColWidth(sheetName, "B", "L", 25) + renderXlsxTableMultiTarget(tableIdx, allTargetsTableValues, targetNames, f, sheetName, &row) + } else { + renderXlsxTableMultiTarget(tableIdx, allTargetsTableValues, targetNames, f, sheetName, &row) + } + } + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + _, err = f.WriteTo(w) + if err != nil { + err = fmt.Errorf("failed to write multi-target xlsx report to buffer: %v", err) + return + } + out = buf.Bytes() + return +} + +func getValueForCell(value string) (val interface{}) { + intValue, err := strconv.Atoi(value) + if err == nil { + val = intValue + return + } + floatValue, err := strconv.ParseFloat(value, 64) + if err == nil { + val = floatValue + return + } + val = value + return +} + +// RawReport represents a raw report containing the target name, table names, and script outputs. +type RawReport struct { + TargetName string // json:"target_name" + TableNames []string // json:"table_names" + ScriptOutputs map[string]script.ScriptOutput // json:"script_outputs" +} + +func CreateRawReport(tableNames []string, scriptOutputs map[string]script.ScriptOutput, targetName string) (out []byte, err error) { + report := RawReport{ + TargetName: targetName, + TableNames: tableNames, + ScriptOutputs: scriptOutputs, + } + out, err = json.MarshalIndent(report, "", " ") + return +} + +// ReadRawReports reads raw reports from the specified path. +// It reads all .raw files in the directory and returns a slice of RawReport. +// If the path is a file, it reads the single raw report and returns it. +func ReadRawReports(path string) (reports []RawReport, err error) { + // path may be a directory or a file + fileInfo, err := os.Stat(path) + if err != nil { + err = fmt.Errorf("failed to get file info: %v", err) + return + } + allRawPaths := []string{} + if fileInfo.IsDir() { + var files []os.DirEntry + files, err = os.ReadDir(path) + if err != nil { + err = fmt.Errorf("failed to read raw report directory: %v", err) + return + } + for _, file := range files { + if file.IsDir() { + continue + } + if strings.HasSuffix(file.Name(), ".raw") { + allRawPaths = append(allRawPaths, path+"/"+file.Name()) + } + } + } else { + allRawPaths = append(allRawPaths, path) + } + for _, rawPath := range allRawPaths { + var report RawReport + report, err = readRawReport(rawPath) + if err != nil { + return + } + reports = append(reports, report) + } + return +} + +func readRawReport(rawReportPath string) (report RawReport, err error) { + reportBytes, err := os.ReadFile(rawReportPath) + if err != nil { + err = fmt.Errorf("failed to read raw report file: %v", err) + return + } + err = json.Unmarshal(reportBytes, &report) + return +} + +// WriteReport writes the report bytes to the specified path. +func WriteReport(reportBytes []byte, reportPath string) error { + err := os.WriteFile(reportPath, reportBytes, 0644) + if err != nil { + err = fmt.Errorf("failed to write report file: %v", err) + fmt.Fprintln(os.Stderr, err) + slog.Error(err.Error()) + return err + } + return nil +} diff --git a/internal/report/stacks_helpers.go b/internal/report/stacks_helpers.go new file mode 100644 index 0000000..9759ddb --- /dev/null +++ b/internal/report/stacks_helpers.go @@ -0,0 +1,165 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "fmt" + "math" + "regexp" + "strconv" + "strings" +) + +// ProcessStacks ... +// [processName][callStack]=count +type ProcessStacks map[string]Stacks + +type Stacks map[string]int + +// example folded stack: +// swapper;secondary_startup_64_no_verify;start_secondary;cpu_startup_entry;arch_cpu_idle_enter 10523019 + +func (p *ProcessStacks) parsePerfFolded(folded string) (err error) { + re := regexp.MustCompile(`^([\w,\-, ,\.]+);(.+) (\d+)$`) + for _, line := range strings.Split(folded, "\n") { + match := re.FindStringSubmatch(line) + if match == nil { + continue + } + processName := match[1] + stack := match[2] + count, err := strconv.Atoi(match[3]) + if err != nil { + continue + } + if _, ok := (*p)[processName]; !ok { + (*p)[processName] = make(Stacks) + } + (*p)[processName][stack] = count + } + return +} + +func (p *ProcessStacks) parseAsyncProfilerFolded(folded string, processName string) (err error) { + for _, line := range strings.Split(folded, "\n") { + splitAt := strings.LastIndex(line, " ") + if splitAt == -1 { + continue + } + stack := line[:splitAt] + count, err := strconv.Atoi(line[splitAt+1:]) + if err != nil { + continue + } + if _, ok := (*p)[processName]; !ok { + (*p)[processName] = make(Stacks) + } + (*p)[processName][stack] = count + } + return +} + +func (p *ProcessStacks) totalSamples() (count int) { + count = 0 + for _, stacks := range *p { + for _, stackCount := range stacks { + count += stackCount + } + } + return +} + +func (p *ProcessStacks) scaleCounts(ratio float64) { + for processName, stacks := range *p { + for stack, stackCount := range stacks { + (*p)[processName][stack] = int(math.Round(float64(stackCount) * ratio)) + } + } +} + +func (p *ProcessStacks) averageDepth(processName string) (average float64) { + if _, ok := (*p)[processName]; !ok { + average = 0 + return + } + total := 0 + count := 0 + for stack := range (*p)[processName] { + total += len(strings.Split(stack, ";")) + count += 1 + } + if count == 0 { + return + } + average = float64(total) / float64(count) + return +} + +func (p *ProcessStacks) dumpFolded() (folded string) { + var sb strings.Builder + for processName, stacks := range *p { + for stack, stackCount := range stacks { + sb.WriteString(fmt.Sprintf("%s;%s %d\n", processName, stack, stackCount)) + } + } + folded = sb.String() + return +} + +// helper functions below + +// mergeJavaFolded -- merge profiles from N java processes +func mergeJavaFolded(javaFolded map[string]string) (merged string, err error) { + javaStacks := make(ProcessStacks) + for processName, stacks := range javaFolded { + err = javaStacks.parseAsyncProfilerFolded(stacks, processName) + if err != nil { + continue + } + } + merged = javaStacks.dumpFolded() + return +} + +// mergeSystemFolded -- merge the two sets of system perf stacks into one set +// For every process, get the average depth of stacks from Fp and Dwarf. +// The stacks with the deepest average (per process) will be retained in the +// merged set. +// The Dwarf stack counts will be scaled to the FP stack counts. +func mergeSystemFolded(perfFp string, perfDwarf string) (merged string, err error) { + fpStacks := make(ProcessStacks) + err = fpStacks.parsePerfFolded(perfFp) + if err != nil { + return + } + dwarfStacks := make(ProcessStacks) + err = dwarfStacks.parsePerfFolded(perfDwarf) + if err != nil { + return + } + fpSampleCount := fpStacks.totalSamples() + dwarfSampleCount := dwarfStacks.totalSamples() + if fpSampleCount == 0 || dwarfSampleCount == 0 { + err = fmt.Errorf("sample counts cannot be zero") + return + } + fpToDwarfScalingRatio := float64(fpSampleCount) / float64(dwarfSampleCount) + dwarfStacks.scaleCounts(fpToDwarfScalingRatio) + + // for every process in fpStacks, get the average stack depth from + // fpStacks and dwarfStacks, choose the deeper stack for the merged set + mergedStacks := make(ProcessStacks) + for processName := range fpStacks { + fpDepth := fpStacks.averageDepth(processName) + dwarfDepth := dwarfStacks.averageDepth(processName) + if fpDepth >= dwarfDepth { + mergedStacks[processName] = fpStacks[processName] + } else { + mergedStacks[processName] = dwarfStacks[processName] + } + } + + merged = mergedStacks.dumpFolded() + return +} diff --git a/internal/report/table_defs.go b/internal/report/table_defs.go new file mode 100644 index 0000000..fbe58e9 --- /dev/null +++ b/internal/report/table_defs.go @@ -0,0 +1,1891 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// table_defs.go defines the tables used for generating reports + +import ( + "fmt" + "log/slog" + "math" + "regexp" + "strconv" + "strings" + + "perfspect/internal/cpudb" + "perfspect/internal/script" + + "github.com/xuri/excelize/v2" +) + +type Insight struct { + Recommendation string + Justification string +} + +type FieldsRetriever func(map[string]script.ScriptOutput) []Field +type InsightsRetriever func(map[string]script.ScriptOutput, TableValues) []Insight +type HTMLTableRenderer func(TableValues, string) string +type HTMLMultiTargetTableRenderer func([]TableValues, []string) string +type TextTableRenderer func(TableValues) string +type XlsxTableRenderer func(TableValues, *excelize.File, string, *int) + +type TableDefinition struct { + Name string + ScriptNames []string + // Fields function is called to retrieve field values from the script outputs + FieldsFunc FieldsRetriever + MenuLabel string // add to tables that will be displayed in the menu + HasRows bool // table is meant to be displayed in row form, i.e., a field may have multiple values + // render functions are used to override the default rendering behavior + HTMLTableRendererFunc HTMLTableRenderer + HTMLMultiTargetTableRendererFunc HTMLMultiTargetTableRenderer + TextTableRendererFunc TextTableRenderer + XlsxTableRendererFunc XlsxTableRenderer + // insights function is used to retrieve insights about the data in the table + InsightsFunc InsightsRetriever +} + +// Field represents the values for a field in a table +type Field struct { + Name string + Values []string +} + +// TableValues combines the table definition with the resulting fields and their values +type TableValues struct { + TableDefinition + Fields []Field + Insights []Insight +} + +const ( + // report table names + HostTableName = "Host" + SystemTableName = "System" + BaseboardTableName = "Baseboard" + ChassisTableName = "Chassis" + PCIeSlotsTableName = "PCIe Slots" + BIOSTableName = "BIOS" + OperatingSystemTableName = "Operating System" + SoftwareVersionTableName = "Software Version" + CPUTableName = "CPU" + ISATableName = "ISA" + AcceleratorTableName = "Accelerator" + PowerTableName = "Power" + CstateTableName = "C-states" + CoreTurboFrequencyTableName = "Core Turbo Frequency" + UncoreTableName = "Uncore" + ElcTableName = "Efficiency Latency Control" + MemoryTableName = "Memory" + DIMMTableName = "DIMM" + NICTableName = "NIC" + NetworkIRQMappingTableName = "Network IRQ Mapping" + DiskTableName = "Disk" + FilesystemTableName = "Filesystem" + GPUTableName = "GPU" + GaudiTableName = "Gaudi" + CXLDeviceTableName = "CXL Device" + CVETableName = "CVE" + ProcessTableName = "Process" + SensorTableName = "Sensor" + ChassisStatusTableName = "Chassis Status" + PMUTableName = "PMU" + SystemEventLogTableName = "System Event Log" + KernelLogTableName = "Kernel Log" + SystemSummaryTableName = "System Summary" + // benchmark table names + CPUSpeedTableName = "CPU Speed" + CPUPowerTableName = "CPU Power" + CPUTemperatureTableName = "CPU Temperature" + CPUFrequencyTableName = "CPU Frequency" + MemoryLatencyTableName = "Memory Latency" + NUMABandwidthTableName = "NUMA Bandwidth" + // telemetry table names + CPUUtilizationTableName = "CPU Utilization" + AverageCPUUtilizationTableName = "Average CPU Utilization" + IRQRateTableName = "IRQ Rate" + DriveStatsTableName = "Drive Stats" + NetworkStatsTableName = "Network Stats" + MemoryStatsTableName = "Memory Stats" + PowerStatsTableName = "Power Stats" + // config table names + ConfigurationTableName = "Configuration" + // flamegraph table names + CodePathFrequencyTableName = "Code Path Frequency" +) + +const ( + // menu labels + HostMenuLabel = "Host" + SoftwareMenuLabel = "Software" + CPUMenuLabel = "CPU" + PowerMenuLabel = "Power" + MemoryMenuLabel = "Memory" + NetworkMenuLabel = "Network" + StorageMenuLabel = "Storage" + DevicesMenuLabel = "Devices" + SecurityMenuLabel = "Security" + StatusMenuLabel = "Status" + LogsMenuLabel = "Logs" + SystemSummaryMenuLabel = "System Summary" +) + +var tableDefinitions = map[string]TableDefinition{ + // + // configuration tables + // + HostTableName: { + Name: HostTableName, + HasRows: false, + MenuLabel: HostMenuLabel, + ScriptNames: []string{ + script.HostnameScriptName, + script.DateScriptName, + script.DmidecodeScriptName}, + FieldsFunc: hostTableValues}, + BIOSTableName: { + Name: BIOSTableName, + HasRows: false, + MenuLabel: SoftwareMenuLabel, + ScriptNames: []string{ + script.DmidecodeScriptName, + }, + FieldsFunc: biosTableValues}, + OperatingSystemTableName: { + Name: OperatingSystemTableName, + HasRows: false, + ScriptNames: []string{ + script.EtcReleaseScriptName, + script.UnameScriptName, + script.ProcCmdlineScriptName, + script.ProcCpuinfoScriptName}, + FieldsFunc: operatingSystemTableValues}, + SoftwareVersionTableName: { + Name: SoftwareVersionTableName, + HasRows: false, + ScriptNames: []string{ + script.GccVersionScriptName, + script.GlibcVersionScriptName, + script.BinutilsVersionScriptName, + script.PythonVersionScriptName, + script.Python3VersionScriptName, + script.JavaVersionScriptName, + script.OpensslVersionScriptName}, + FieldsFunc: softwareVersionTableValues}, + CPUTableName: { + Name: CPUTableName, + HasRows: false, + MenuLabel: CPUMenuLabel, + ScriptNames: []string{ + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName, + script.CpuidScriptName, + script.BaseFrequencyScriptName, + script.MaximumFrequencyScriptName, + script.SpecTurboFrequenciesScriptName, + script.SpecTurboCoresScriptName, + script.PPINName, + script.PrefetchControlName, + script.PrefetchersName, + script.L3WaySizeName}, + FieldsFunc: cpuTableValues, + InsightsFunc: cpuTableInsights}, + ISATableName: { + Name: ISATableName, + ScriptNames: []string{script.CpuidScriptName}, + FieldsFunc: isaTableValues}, + AcceleratorTableName: { + Name: AcceleratorTableName, + HasRows: true, + ScriptNames: []string{ + script.LshwScriptName, + script.IaaDevicesScriptName, + script.DsaDevicesScriptName}, + FieldsFunc: acceleratorTableValues, + InsightsFunc: acceleratorTableInsights}, + PowerTableName: { + Name: PowerTableName, + HasRows: false, + MenuLabel: PowerMenuLabel, + ScriptNames: []string{ + script.PackagePowerLimitName, + script.EpbScriptName, + script.EppScriptName, + script.EppValidScriptName, + script.EppPackageScriptName, + script.ScalingDriverScriptName, + script.ScalingGovernorScriptName}, + FieldsFunc: powerTableValues, + InsightsFunc: powerTableInsights}, + CstateTableName: { + Name: CstateTableName, + HasRows: true, + ScriptNames: []string{ + script.CstatesScriptName, + }, + FieldsFunc: cstateTableValues}, + CoreTurboFrequencyTableName: { + Name: CoreTurboFrequencyTableName, + HasRows: true, + ScriptNames: []string{ + script.SpecTurboFrequenciesScriptName, + script.SpecTurboCoresScriptName, + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName, + }, + FieldsFunc: coreTurboFrequencyTableValues, + HTMLTableRendererFunc: coreTurboFrequencyTableHTMLRenderer}, + UncoreTableName: { + Name: UncoreTableName, + HasRows: false, + ScriptNames: []string{ + script.UncoreMaxFromMSRScriptName, + script.UncoreMinFromMSRScriptName, + script.UncoreMaxFromTPMIScriptName, + script.UncoreMinFromTPMIScriptName, + script.ChaCountScriptName, + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName}, + FieldsFunc: uncoreTableValues}, + ElcTableName: { + Name: ElcTableName, + HasRows: true, + ScriptNames: []string{ + script.ElcScriptName, + }, + FieldsFunc: elcTableValues, + InsightsFunc: elcTableInsights}, + MemoryTableName: { + Name: MemoryTableName, + HasRows: false, + MenuLabel: MemoryMenuLabel, + ScriptNames: []string{ + script.DmidecodeScriptName, + script.MeminfoScriptName, + script.TransparentHugePagesScriptName, + script.NumaBalancingScriptName, + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName}, + FieldsFunc: memoryTableValues, + InsightsFunc: memoryTableInsights}, + DIMMTableName: { + Name: DIMMTableName, + HasRows: true, + ScriptNames: []string{ + script.DmidecodeScriptName, + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName, + }, + FieldsFunc: dimmTableValues, + InsightsFunc: dimmTableInsights, + HTMLTableRendererFunc: dimmTableHTMLRenderer}, + NICTableName: { + Name: NICTableName, + HasRows: true, + MenuLabel: NetworkMenuLabel, + ScriptNames: []string{ + script.NicInfoScriptName, + script.LshwScriptName, + }, + FieldsFunc: nicTableValues}, + NetworkIRQMappingTableName: { + Name: NetworkIRQMappingTableName, + HasRows: true, + ScriptNames: []string{ + script.LshwScriptName, + script.NicInfoScriptName, + }, + FieldsFunc: networkIRQMappingTableValues}, + DiskTableName: { + Name: DiskTableName, + HasRows: true, + MenuLabel: StorageMenuLabel, + ScriptNames: []string{ + script.DiskInfoScriptName, + script.HdparmScriptName, + }, + FieldsFunc: diskTableValues}, + FilesystemTableName: { + Name: FilesystemTableName, + HasRows: true, + ScriptNames: []string{ + script.DfScriptName, + script.FindMntScriptName, + }, + FieldsFunc: filesystemTableValues, + InsightsFunc: filesystemTableInsights}, + GPUTableName: { + Name: GPUTableName, + HasRows: true, + MenuLabel: DevicesMenuLabel, + ScriptNames: []string{ + script.LshwScriptName, + }, + FieldsFunc: gpuTableValues}, + GaudiTableName: { + Name: GaudiTableName, + HasRows: true, + ScriptNames: []string{ + script.GaudiInfoScriptName, + script.GaudiFirmwareScriptName, + script.GaudiNumaScriptName, + }, + FieldsFunc: gaudiTableValues}, + CXLDeviceTableName: { + Name: CXLDeviceTableName, + HasRows: true, + ScriptNames: []string{ + script.LspciVmmScriptName, + }, + FieldsFunc: cxlDeviceTableValues}, + PCIeSlotsTableName: { + Name: PCIeSlotsTableName, + HasRows: true, + ScriptNames: []string{ + script.DmidecodeScriptName, + }, + FieldsFunc: pcieSlotsTableValues}, + CVETableName: { + Name: CVETableName, + MenuLabel: SecurityMenuLabel, + ScriptNames: []string{ + script.CveScriptName, + }, + FieldsFunc: cveTableValues, + InsightsFunc: cveTableInsights}, + ProcessTableName: { + Name: ProcessTableName, + HasRows: true, + MenuLabel: StatusMenuLabel, + ScriptNames: []string{ + script.ProcessListScriptName, + }, + FieldsFunc: processTableValues}, + SensorTableName: { + Name: SensorTableName, + HasRows: true, + ScriptNames: []string{ + script.IpmitoolSensorsScriptName, + }, + FieldsFunc: sensorTableValues}, + ChassisStatusTableName: { + Name: ChassisStatusTableName, + HasRows: false, + ScriptNames: []string{ + script.IpmitoolChassisScriptName, + script.IpmitoolEventTimeScriptName, + }, + FieldsFunc: chassisStatusTableValues}, + PMUTableName: { + Name: PMUTableName, + HasRows: false, + ScriptNames: []string{ + script.PMUBusyScriptName, + script.PMUDriverVersionScriptName, + }, + FieldsFunc: pmuTableValues}, + SystemEventLogTableName: { + Name: SystemEventLogTableName, + HasRows: true, + MenuLabel: LogsMenuLabel, + ScriptNames: []string{ + script.IpmitoolEventsScriptName, + }, + FieldsFunc: systemEventLogTableValues, + InsightsFunc: systemEventLogTableInsights}, + KernelLogTableName: { + Name: KernelLogTableName, + HasRows: true, + ScriptNames: []string{ + script.KernelLogScriptName, + }, + FieldsFunc: kernelLogTableValues}, + SystemSummaryTableName: { + Name: SystemSummaryTableName, + HasRows: false, + MenuLabel: SystemSummaryMenuLabel, + ScriptNames: []string{ + script.HostnameScriptName, + script.DateScriptName, + script.DmidecodeScriptName, + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName, + script.CpuidScriptName, + script.BaseFrequencyScriptName, + script.SpecTurboCoresScriptName, + script.SpecTurboFrequenciesScriptName, + script.PrefetchControlName, + script.PrefetchersName, + script.PPINName, + script.LshwScriptName, + script.MeminfoScriptName, + script.TransparentHugePagesScriptName, + script.NumaBalancingScriptName, + script.NicInfoScriptName, + script.DiskInfoScriptName, + script.ProcCpuinfoScriptName, + script.UnameScriptName, + script.EtcReleaseScriptName, + script.PackagePowerLimitName, + script.EpbScriptName, + script.ScalingDriverScriptName, + script.ScalingGovernorScriptName, + script.CstatesScriptName, + script.ElcScriptName, + script.CveScriptName, + }, + FieldsFunc: systemSummaryTableValues}, + // + // configuration set table + // + ConfigurationTableName: { + Name: ConfigurationTableName, + HasRows: false, + ScriptNames: []string{ + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName, + script.L3WaySizeName, + script.PackagePowerLimitName, + script.EpbScriptName, + script.EppScriptName, + script.EppValidScriptName, + script.EppPackageScriptName, + script.ScalingGovernorScriptName, + script.UncoreMaxFromMSRScriptName, + script.UncoreMinFromMSRScriptName, + script.UncoreMaxFromTPMIScriptName, + script.UncoreMinFromTPMIScriptName, + script.SpecTurboFrequenciesScriptName, + script.SpecTurboCoresScriptName, + script.ElcScriptName, + }, + FieldsFunc: configurationTableValues}, + // + // benchmarking tables + // + CPUSpeedTableName: { + Name: CPUSpeedTableName, + MenuLabel: CPUSpeedTableName, + HasRows: false, + ScriptNames: []string{ + script.CpuSpeedScriptName, + }, + FieldsFunc: cpuSpeedTableValues}, + CPUPowerTableName: { + Name: CPUPowerTableName, + MenuLabel: CPUPowerTableName, + HasRows: false, + ScriptNames: []string{ + script.IdlePowerScriptName, + script.TurboFrequencyPowerAndTemperatureScriptName, + }, + FieldsFunc: cpuPowerTableValues}, + CPUTemperatureTableName: { + Name: CPUTemperatureTableName, + MenuLabel: CPUTemperatureTableName, + HasRows: false, + ScriptNames: []string{ + script.TurboFrequencyPowerAndTemperatureScriptName, + }, + FieldsFunc: cpuTemperatureTableValues}, + CPUFrequencyTableName: { + Name: CPUFrequencyTableName, + MenuLabel: CPUFrequencyTableName, + HasRows: true, + ScriptNames: []string{ + script.SpecTurboFrequenciesScriptName, + script.SpecTurboCoresScriptName, + script.LscpuScriptName, + script.LspciBitsScriptName, + script.LspciDevicesScriptName, + script.TurboFrequenciesScriptName, + }, + FieldsFunc: cpuFrequencyTableValues, + HTMLTableRendererFunc: cpuFrequencyTableHtmlRenderer}, + MemoryLatencyTableName: { + Name: MemoryLatencyTableName, + MenuLabel: MemoryLatencyTableName, + HasRows: true, + ScriptNames: []string{ + script.MemoryBandwidthAndLatencyScriptName, + }, + FieldsFunc: memoryLatencyTableValues, + HTMLTableRendererFunc: memoryLatencyTableHtmlRenderer, + HTMLMultiTargetTableRendererFunc: memoryLatencyTableMultiTargetHtmlRenderer}, + NUMABandwidthTableName: { + Name: NUMABandwidthTableName, + MenuLabel: NUMABandwidthTableName, + HasRows: true, + ScriptNames: []string{ + script.NumaBandwidthScriptName, + }, + FieldsFunc: numaBandwidthTableValues}, + // + // telemetry tables + // + CPUUtilizationTableName: { + Name: CPUUtilizationTableName, + MenuLabel: CPUUtilizationTableName, + HasRows: true, + ScriptNames: []string{ + script.MpstatScriptName, + }, + FieldsFunc: cpuUtilizationTableValues, + HTMLTableRendererFunc: cpuUtilizationTableHTMLRenderer}, + AverageCPUUtilizationTableName: { + Name: AverageCPUUtilizationTableName, + MenuLabel: AverageCPUUtilizationTableName, + HasRows: true, + ScriptNames: []string{ + script.MpstatScriptName, + }, + FieldsFunc: averageCPUUtilizationTableValues, + HTMLTableRendererFunc: averageCPUUtilizationTableHTMLRenderer}, + IRQRateTableName: { + Name: IRQRateTableName, + MenuLabel: IRQRateTableName, + HasRows: true, + ScriptNames: []string{ + script.MpstatScriptName, + }, + FieldsFunc: irqRateTableValues, + HTMLTableRendererFunc: irqRateTableHTMLRenderer}, + DriveStatsTableName: { + Name: DriveStatsTableName, + MenuLabel: DriveStatsTableName, + HasRows: true, + ScriptNames: []string{ + script.IostatScriptName, + }, + FieldsFunc: driveStatsTableValues, + HTMLTableRendererFunc: driveStatsTableHTMLRenderer}, + NetworkStatsTableName: { + Name: NetworkStatsTableName, + MenuLabel: NetworkStatsTableName, + HasRows: true, + ScriptNames: []string{ + script.SarNetworkScriptName, + }, + FieldsFunc: networkStatsTableValues, + HTMLTableRendererFunc: networkStatsTableHTMLRenderer}, + MemoryStatsTableName: { + Name: MemoryStatsTableName, + MenuLabel: MemoryStatsTableName, + HasRows: true, + ScriptNames: []string{ + script.SarMemoryScriptName, + }, + FieldsFunc: memoryStatsTableValues, + HTMLTableRendererFunc: memoryStatsTableHTMLRenderer}, + PowerStatsTableName: { + Name: PowerStatsTableName, + MenuLabel: PowerStatsTableName, + HasRows: true, + ScriptNames: []string{ + script.TurbostatScriptName, + }, + FieldsFunc: powerStatsTableValues, + HTMLTableRendererFunc: powerStatsTableHTMLRenderer}, + // + // flamegraph tables + // + CodePathFrequencyTableName: { + Name: CodePathFrequencyTableName, + ScriptNames: []string{ + script.ProfileJavaScriptName, + script.ProfileSystemScriptName, + }, + FieldsFunc: codePathFrequencyTableValues, + HTMLTableRendererFunc: codePathFrequencyTableHTMLRenderer}, +} + +// GetScriptNamesForTable returns the script names required to generate the table with the given name +func GetScriptNamesForTable(name string) []string { + if _, ok := tableDefinitions[name]; !ok { + panic(fmt.Sprintf("table not found: %s", name)) + } + return tableDefinitions[name].ScriptNames +} + +// GetValuesForTable returns the fields and their values for the table with the given name +func GetValuesForTable(name string, outputs map[string]script.ScriptOutput) TableValues { + // if table with given name doesn't exist, panic + if _, ok := tableDefinitions[name]; !ok { + panic(fmt.Sprintf("table not found: %s", name)) + } + table := tableDefinitions[name] + // ValuesFunc can't be nil + if table.FieldsFunc == nil { + panic(fmt.Sprintf("table %s, ValuesFunc cannot be nil", name)) + } + // call the table's FieldsFunc to get the table's fields and values + fields := table.FieldsFunc(outputs) + tableValues := TableValues{ + TableDefinition: tableDefinitions[name], + Fields: fields, + } + // sanity check + validateTableValues(tableValues) + // call the table's InsightsFunc to get insights about the data in the table + if table.InsightsFunc != nil { + tableValues.Insights = table.InsightsFunc(outputs, tableValues) + } + return tableValues +} + +func getFieldIndex(fieldName string, tableValues TableValues) (int, error) { + for i, field := range tableValues.Fields { + if field.Name == fieldName { + if len(field.Values) == 0 { + return -1, fmt.Errorf("field [%s] does not have associated value(s)", field.Name) + } + return i, nil + } + } + return -1, fmt.Errorf("field [%s] not found in table [%s]", fieldName, tableValues.Name) +} + +func validateTableValues(tableValues TableValues) { + if tableValues.Name == "" { + panic("table name cannot be empty") + } + // no field values is a valid state + if len(tableValues.Fields) == 0 { + return + } + // field names cannot be empty + for i, field := range tableValues.Fields { + if field.Name == "" { + panic(fmt.Sprintf("table %s, field %d, name cannot be empty", tableValues.Name, i)) + } + } + // the number of entries in each field must be the same + numEntries := len(tableValues.Fields[0].Values) + for i, field := range tableValues.Fields { + if len(field.Values) != numEntries { + panic(fmt.Sprintf("table %s, field %d, %s, number of entries must be the same for all fields", tableValues.Name, i, field.Name)) + } + } +} + +// +// define the fieldsFunc for each table +// + +func hostTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Host Name", Values: []string{strings.TrimSpace(outputs[script.HostnameScriptName].Stdout)}}, + {Name: "Time", Values: []string{strings.TrimSpace(outputs[script.DateScriptName].Stdout)}}, + {Name: "System", Values: []string{valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "1", `^Manufacturer:\s*(.+?)$`) + " " + valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "1", `^Product Name:\s*(.+?)$`)}}, + {Name: "Baseboard", Values: []string{valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "2", `^Manufacturer:\s*(.+?)$`) + " " + valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "2", `^Product Name:\s*(.+?)$`)}}, + {Name: "Chassis", Values: []string{valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "3", `^Manufacturer:\s*(.+?)$`) + " " + valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "3", `^Type:\s*(.+?)$`)}}, + } +} + +func pcieSlotsTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Designation"}, + {Name: "Type"}, + {Name: "Length"}, + {Name: "Bus Address"}, + {Name: "Current Usage"}, + } + fieldValues := valsArrayFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "9", + []string{ + `^Designation:\s*(.+?)$`, + `^Type:\s*(.+?)$`, + `^Length:\s*(.+?)$`, + `^Bus Address:\s*(.+?)$`, + `^Current Usage:\s*(.+?)$`, + }..., + ) + for i := range fields { + for j := range fieldValues { + fields[i].Values = append(fields[i].Values, fieldValues[j][i]) + } + } + return fields +} + +func biosTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Vendor"}, + {Name: "Version"}, + {Name: "Release Date"}, + } + fieldValues := valsArrayFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "0", + []string{ + `^Vendor:\s*(.+?)$`, + `^Version:\s*(.+?)$`, + `^Release Date:\s*(.+?)$`, + }..., + ) + for i := range fields { + for j := range fieldValues { + fields[i].Values = append(fields[i].Values, fieldValues[j][i]) + } + } + return fields +} + +func operatingSystemTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "OS", Values: []string{operatingSystemFromOutput(outputs)}}, + {Name: "Kernel", Values: []string{valFromRegexSubmatch(outputs[script.UnameScriptName].Stdout, `^Linux \S+ (\S+)`)}}, + {Name: "Boot Parameters", Values: []string{strings.TrimSpace(outputs[script.ProcCmdlineScriptName].Stdout)}}, + {Name: "Microcode", Values: []string{valFromRegexSubmatch(outputs[script.ProcCpuinfoScriptName].Stdout, `^microcode.*:\s*(.+?)$`)}}, + } +} + +func softwareVersionTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "GCC", Values: []string{valFromRegexSubmatch(outputs[script.GccVersionScriptName].Stdout, `^(gcc .*)$`)}}, + {Name: "GLIBC", Values: []string{valFromRegexSubmatch(outputs[script.GlibcVersionScriptName].Stdout, `^(ldd .*)`)}}, + {Name: "Binutils", Values: []string{valFromRegexSubmatch(outputs[script.BinutilsVersionScriptName].Stdout, `^(GNU ld .*)$`)}}, + {Name: "Python", Values: []string{valFromRegexSubmatch(outputs[script.PythonVersionScriptName].Stdout, `^(Python .*)$`)}}, + {Name: "Python3", Values: []string{valFromRegexSubmatch(outputs[script.Python3VersionScriptName].Stdout, `^(Python 3.*)$`)}}, + {Name: "Java", Values: []string{valFromRegexSubmatch(outputs[script.JavaVersionScriptName].Stdout, `^(openjdk .*)$`)}}, + {Name: "OpenSSL", Values: []string{valFromRegexSubmatch(outputs[script.OpensslVersionScriptName].Stdout, `^(OpenSSL .*)$`)}}, + } +} + +func cpuTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "CPU Model", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^[Mm]odel name:\s*(.+)$`)}}, + {Name: "Architecture", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Architecture:\s*(.+)$`)}}, + {Name: "Microarchitecture", Values: []string{uarchFromOutput(outputs)}}, + {Name: "Family", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU family:\s*(.+)$`)}}, + {Name: "Model", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Model:\s*(.+)$`)}}, + {Name: "Stepping", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Stepping:\s*(.+)$`)}}, + {Name: "Base Frequency", Values: []string{baseFrequencyFromOutput(outputs)}}, + {Name: "Maximum Frequency", Values: []string{maxFrequencyFromOutput(outputs)}}, + {Name: "All-core Maximum Frequency", Values: []string{allCoreMaxFrequencyFromOutput(outputs)}}, + {Name: "CPUs", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU\(s\):\s*(.+)$`)}}, + {Name: "On-line CPU List", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^On-line CPU\(s\) list:\s*(.+)$`)}}, + {Name: "Hyperthreading", Values: []string{hyperthreadingFromOutput(outputs)}}, + {Name: "Cores per Socket", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(.+)$`)}}, + {Name: "Sockets", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(s\):\s*(.+)$`)}}, + {Name: "NUMA Nodes", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^NUMA node\(s\):\s*(.+)$`)}}, + {Name: "NUMA CPU List", Values: []string{numaCPUListFromOutput(outputs)}}, + {Name: "L1d Cache", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^L1d cache:\s*(.+)$`)}}, + {Name: "L1i Cache", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^L1i cache:\s*(.+)$`)}}, + {Name: "L2 Cache", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^L2 cache:\s*(.+)$`)}}, + {Name: "L3 Cache", Values: []string{l3FromOutput(outputs)}}, + {Name: "L3 per Core", Values: []string{l3PerCoreFromOutput(outputs)}}, + {Name: "Memory Channels", Values: []string{channelsFromOutput(outputs)}}, + {Name: "Prefetchers", Values: []string{prefetchersFromOutput(outputs)}}, + {Name: "Intel Turbo Boost", Values: []string{turboEnabledFromOutput(outputs)}}, + {Name: "Virtualization", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Virtualization:\s*(.+)$`)}}, + {Name: "PPINs", Values: []string{ppinsFromOutput(outputs)}}, + } +} + +func cpuTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + addInsightFunc := func(fieldName, bestValue string) { + fieldIndex, err := getFieldIndex(fieldName, tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + fieldValue := tableValues.Fields[fieldIndex].Values[0] + if fieldValue != "" && fieldValue != "N/A" && fieldValue != bestValue { + insights = append(insights, Insight{ + Recommendation: fmt.Sprintf("Consider enabling %s.", fieldName), + Justification: fmt.Sprintf("%s is not enabled.", fieldName), + }) + } + } + } + addInsightFunc("Hyperthreading", "Enabled") + addInsightFunc("Intel Turbo Boost", "Enabled") + // Xeon Generation + familyIndex, err := getFieldIndex("Family", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + family := tableValues.Fields[familyIndex].Values[0] + if family == "6" { // Intel + uarchIndex, err := getFieldIndex("Microarchitecture", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + xeonGens := map[string]int{ + "HSX": 1, + "BDX": 2, + "SKX": 3, + "CLX": 4, + "ICX": 5, + "SPR": 6, + "EMR": 7, + "SRF": 8, + "GNR": 8, + } + uarch := tableValues.Fields[uarchIndex].Values[0] + if len(uarch) >= 3 { + xeonGen, ok := xeonGens[uarch[:3]] + if ok { + if xeonGen < xeonGens["SPR"] { + insights = append(insights, Insight{ + Recommendation: "Consider upgrading to the latest generation Intel(r) Xeon(r) CPU.", + Justification: "The CPU is 2 or more generations behind the latest Intel(r) Xeon(r) CPU.", + }) + } + } + } + } + } else { + insights = append(insights, Insight{ + Recommendation: "Consider upgrading to an Intel(r) Xeon(r) CPU.", + Justification: "The current CPU is not an Intel(r) Xeon(r) CPU.", + }) + } + } + return insights +} + +func isaTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{} + supported := isaSupportedFromOutput(outputs) + for i, isa := range isaFullNames() { + fields = append(fields, Field{ + Name: isa, + Values: []string{supported[i]}, + }) + } + return fields +} + +func acceleratorTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Name", Values: acceleratorNames()}, + {Name: "Count", Values: acceleratorCountsFromOutput(outputs)}, + {Name: "Work Queues", Values: acceleratorWorkQueuesFromOutput(outputs)}, + {Name: "Full Name", Values: acceleratorFullNamesFromYaml()}, + {Name: "Description", Values: acceleratorDescriptionsFromYaml()}, + } +} + +func acceleratorTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + nameFieldIndex, err := getFieldIndex("Name", tableValues) + if err != nil { + slog.Warn(err.Error()) + return insights + } + countFieldIndex, err := getFieldIndex("Count", tableValues) + if err != nil { + slog.Warn(err.Error()) + return insights + } + queuesFieldIndex, err := getFieldIndex("Work Queues", tableValues) + if err != nil { + slog.Warn(err.Error()) + return insights + } + for i, count := range tableValues.Fields[countFieldIndex].Values { + name := tableValues.Fields[nameFieldIndex].Values[i] + queues := tableValues.Fields[queuesFieldIndex].Values[i] + if name == "DSA" && count != "0" && queues != "None" { + insights = append(insights, Insight{ + Recommendation: "Consider configuring DSA to allow accelerated data copy and transformation in DSA-enabled software.", + Justification: "No work queues are configured for DSA accelerator(s).", + }) + } + if name == "IAA" && count != "0" && queues != "None" { + insights = append(insights, Insight{ + Recommendation: "Consider configuring IAA to allow accelerated compression and decompression in IAA-enabled software.", + Justification: "No work queues are configured for IAA accelerator(s).", + }) + } + } + return insights +} + +func powerTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "TDP", Values: []string{tdpFromOutput(outputs)}}, + {Name: "Energy Performance Bias", Values: []string{epbFromOutput(outputs)}}, + {Name: "Energy Performance Preference", Values: []string{eppFromOutput(outputs)}}, + {Name: "Scaling Governor", Values: []string{strings.TrimSpace(outputs[script.ScalingGovernorScriptName].Stdout)}}, + {Name: "Scaling Driver", Values: []string{strings.TrimSpace(outputs[script.ScalingDriverScriptName].Stdout)}}, + } +} + +func powerTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + addInsightFunc := func(fieldName, bestValue string) { + fieldIndex, err := getFieldIndex(fieldName, tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + fieldValue := tableValues.Fields[fieldIndex].Values[0] + if fieldValue != "" && fieldValue != bestValue { + insights = append(insights, Insight{ + Recommendation: fmt.Sprintf("Consider setting %s to '%s'.", fieldName, bestValue), + Justification: fmt.Sprintf("%s is set to '%s'", fieldName, fieldValue), + }) + } + } + } + addInsightFunc("Scaling Governor", "performance") + addInsightFunc("Scaling Driver", "intel_pstate") + addInsightFunc("Energy Performance Bias", "Performance (0)") + addInsightFunc("Energy Performance Preference", "Performance (0)") + return insights +} + +func cstateTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Name"}, + {Name: "Status"}, // enabled/disabled + } + cstates := cstatesFromOutput(outputs) + for _, cstateInfo := range cstates { + fields[0].Values = append(fields[0].Values, cstateInfo.Name) + fields[1].Values = append(fields[1].Values, cstateInfo.Status) + } + return fields +} + +func uncoreTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Min Frequency", Values: []string{uncoreMinFrequencyFromOutput(outputs)}}, + {Name: "Max Frequency", Values: []string{uncoreMaxFrequencyFromOutput(outputs)}}, + {Name: "CHA Count", Values: []string{chaCountFromOutput(outputs)}}, + } +} + +func elcTableValues(outputs map[string]script.ScriptOutput) []Field { + return elcFieldValuesFromOutput(outputs) +} + +func elcTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + modeFieldIndex, err := getFieldIndex("Mode", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + for _, mode := range tableValues.Fields[modeFieldIndex].Values { + if mode != "" && mode != "Latency Optimized" { + insights = append(insights, Insight{ + Recommendation: "Consider setting Efficiency Latency Control mode to 'Latency Optimized' when workload is highly sensitive to memory latency.", + Justification: fmt.Sprintf("ELC mode is set to '%s' on at least one die.", mode), + }) + break + } + } + for _, mode := range tableValues.Fields[modeFieldIndex].Values { + if mode != "" && mode != "Default" { + insights = append(insights, Insight{ + Recommendation: "Consider setting Efficiency Latency Control mode to 'Default' to balance uncore performance and power utilization.", + Justification: fmt.Sprintf("ELC mode is set to '%s' on at least one die.", mode), + }) + break + } + } + } + return insights +} + +func coreTurboFrequencyTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Active Cores", Values: []string{}}, + {Name: "non-avx:spec", Values: []string{}}, + } + bins, err := getSpecCountFrequencies(outputs) + if err != nil { + slog.Warn("unable to get spec count frequencies", slog.String("error", err.Error())) + return fields + } + coresPerSocket := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(\d+)$`) + coreCount, err := strconv.Atoi(coresPerSocket) + if err != nil { + slog.Warn("unable to get core count", slog.String("error", err.Error())) + return fields + } + counts := []string{} + frequencies := []string{} + for i := 1; i <= coreCount; i++ { + counts = append(counts, strconv.Itoa(i)) + } + beginInt := 0 + for _, bin := range bins { + count := bin[0] + endInt, err := strconv.Atoi(count) + if err != nil { + slog.Warn("unable to convert count to int", slog.String("count", count), slog.String("error", err.Error())) + return fields + } + endInt -= 1 + endInt = int(math.Min(float64(endInt), float64(coreCount-1))) + for i := beginInt; i <= endInt; i++ { + frequencies = append(frequencies, bin[1]) + } + beginInt = endInt + 1 + } + fields[0].Values = counts + fields[1].Values = frequencies + return fields +} + +func memoryTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Installed Memory", Values: []string{installedMemoryFromOutput(outputs)}}, + {Name: "MemTotal", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemTotal:\s*(.+?)$`)}}, + {Name: "MemFree", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemFree:\s*(.+?)$`)}}, + {Name: "MemAvailable", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemAvailable:\s*(.+?)$`)}}, + {Name: "Buffers", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^Buffers:\s*(.+?)$`)}}, + {Name: "Cached", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^Cached:\s*(.+?)$`)}}, + {Name: "HugePages_Total", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^HugePages_Total:\s*(.+?)$`)}}, + {Name: "Hugepagesize", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^Hugepagesize:\s*(.+?)$`)}}, + {Name: "Transparent Huge Pages", Values: []string{valFromRegexSubmatch(outputs[script.TransparentHugePagesScriptName].Stdout, `.*\[(.*)\].*`)}}, + {Name: "Automatic NUMA Balancing", Values: []string{numaBalancingFromOutput(outputs)}}, + {Name: "Populated Memory Channels", Values: []string{populatedChannelsFromOutput(outputs)}}, + } +} + +func memoryTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + // check if memory is not fully populated + populatedChannelsIndex, err := getFieldIndex("Populated Memory Channels", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + populatedChannels := tableValues.Fields[populatedChannelsIndex].Values[0] + if populatedChannels != "" { + uarch := uarchFromOutput(outputs) + if uarch != "" { + CPUdb := cpudb.NewCPUDB() + cpu, err := CPUdb.GetCPUByMicroArchitecture(uarch) + if err != nil { + slog.Warn(err.Error()) + } else { + sockets := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(s\):\s*(.+)$`) + socketCount, err := strconv.Atoi(sockets) + if err != nil { + slog.Warn(err.Error()) + } else { + totalMemoryChannels := socketCount * cpu.MemoryChannelCount + if populatedChannels != strconv.Itoa(totalMemoryChannels) { + insights = append(insights, Insight{ + Recommendation: fmt.Sprintf("Consider populating all (%d) memory channels.", totalMemoryChannels), + Justification: fmt.Sprintf("%s memory channels are populated.", populatedChannels), + }) + } + } + } + } + } + } + // check if NUMA balancing is not enabled + numaBalancingIndex, err := getFieldIndex("Automatic NUMA Balancing", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + numaBalancing := tableValues.Fields[numaBalancingIndex].Values[0] + if numaBalancing != "" && numaBalancing != "Enabled" { + insights = append(insights, Insight{ + Recommendation: "Consider enabling Automatic NUMA Balancing.", + Justification: "Automatic NUMA Balancing is not enabled.", + }) + } + } + return insights +} + +func dimmTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Bank Locator"}, + {Name: "Locator"}, + {Name: "Manufacturer"}, + {Name: "Part"}, + {Name: "Serial"}, + {Name: "Size"}, + {Name: "Type"}, + {Name: "Detail"}, + {Name: "Speed"}, + {Name: "Rank"}, + {Name: "Configured Speed"}, + {Name: "Socket"}, + {Name: "Channel"}, + {Name: "Slot"}, + } + dimmFieldValues := valsArrayFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "17", + []string{ + `^Bank Locator:\s*(.+?)$`, + `^Locator:\s*(.+?)$`, + `^Manufacturer:\s*(.+?)$`, + `^Part Number:\s*(.+?)\s*$`, + `^Serial Number:\s*(.+?)\s*$`, + `^Size:\s*(.+?)$`, + `^Type:\s*(.+?)$`, + `^Type Detail:\s*(.+?)$`, + `^Speed:\s*(.+?)$`, + `^Rank:\s*(.+?)$`, + `^Configured.*Speed:\s*(.+?)$`, + }..., + ) + for dimmIndex := range dimmFieldValues { + for fieldIndex := 0; fieldIndex <= 10; fieldIndex++ { + fields[fieldIndex].Values = append(fields[fieldIndex].Values, dimmFieldValues[dimmIndex][fieldIndex]) + } + } + derivedDimmFieldValues := derivedDimmsFieldFromOutput(outputs) + if len(dimmFieldValues) != len(derivedDimmFieldValues) { + slog.Warn("unable to derive socket, channel, and slot for all DIMMs") + // fill with empty strings + fields[11].Values = append(fields[11].Values, make([]string, len(dimmFieldValues))...) + fields[12].Values = append(fields[12].Values, make([]string, len(dimmFieldValues))...) + fields[13].Values = append(fields[13].Values, make([]string, len(dimmFieldValues))...) + } else { + for i := range derivedDimmFieldValues { + fields[11].Values = append(fields[11].Values, derivedDimmFieldValues[i].socket) + fields[12].Values = append(fields[12].Values, derivedDimmFieldValues[i].channel) + fields[13].Values = append(fields[13].Values, derivedDimmFieldValues[i].slot) + } + } + return fields +} + +func dimmTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + // check if are configured for their maximum speed + SpeedIndex, err := getFieldIndex("Speed", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + ConfiguredSpeedIndex, err := getFieldIndex("Configured Speed", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + for i, speed := range tableValues.Fields[SpeedIndex].Values { + configuredSpeed := tableValues.Fields[ConfiguredSpeedIndex].Values[i] + if speed != "" && configuredSpeed != "" && speed != "Unknown" && configuredSpeed != "Unknown" { + speedVal, err := strconv.Atoi(strings.Split(speed, " ")[0]) + if err != nil { + slog.Warn(err.Error()) + } else { + configuredSpeedVal, err := strconv.Atoi(strings.Split(configuredSpeed, " ")[0]) + if err != nil { + slog.Warn(err.Error()) + } else { + if speedVal < configuredSpeedVal { + insights = append(insights, Insight{ + Recommendation: "Consider configuring DIMMs for their maximum speed.", + Justification: fmt.Sprintf("DIMMs configured for %s when their maximum speed is %s.", configuredSpeed, speed), + }) + } + } + } + } + } + } + } + return insights +} + +func nicTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Name"}, + {Name: "Model"}, + {Name: "Speed"}, + {Name: "Link"}, + {Name: "Bus"}, + {Name: "Driver"}, + {Name: "Driver Version"}, + {Name: "Firmware Version"}, + {Name: "MAC Address"}, + {Name: "NUMA Node"}, + {Name: "IRQBalance"}, + } + allNicsInfo := nicInfoFromOutput(outputs) + for _, nicInfo := range allNicsInfo { + fields[0].Values = append(fields[0].Values, nicInfo.Name) + fields[1].Values = append(fields[1].Values, nicInfo.Model) + fields[2].Values = append(fields[2].Values, nicInfo.Speed) + fields[3].Values = append(fields[3].Values, nicInfo.Link) + fields[4].Values = append(fields[4].Values, nicInfo.Bus) + fields[5].Values = append(fields[5].Values, nicInfo.Driver) + fields[6].Values = append(fields[6].Values, nicInfo.DriverVersion) + fields[7].Values = append(fields[7].Values, nicInfo.FirmwareVersion) + fields[8].Values = append(fields[8].Values, nicInfo.MACAddress) + fields[9].Values = append(fields[9].Values, nicInfo.NUMANode) + fields[10].Values = append(fields[10].Values, nicInfo.IRQBalance) + } + return fields +} + +func networkIRQMappingTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Interface"}, + {Name: "CPU:IRQs CPU:IRQs ..."}, + } + nicIRQMappings := nicIRQMappingsFromOutput(outputs) + for _, nicIRQMapping := range nicIRQMappings { + fields[0].Values = append(fields[0].Values, nicIRQMapping[0]) + fields[1].Values = append(fields[1].Values, nicIRQMapping[1]) + } + return fields +} + +func diskTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Name"}, + {Name: "Model"}, + {Name: "Size"}, + {Name: "Mount Point"}, + {Name: "Type"}, + {Name: "Request Queue Size"}, + {Name: "Minimum I/O Size"}, + {Name: "Firmware Version"}, + {Name: "PCIe Address"}, + {Name: "NUMA Node"}, + {Name: "Link Speed"}, + {Name: "Link Width"}, + {Name: "Max Link Speed"}, + {Name: "Max Link Width"}, + } + allDisksInfo := diskInfoFromOutput(outputs) + for _, diskInfo := range allDisksInfo { + fields[0].Values = append(fields[0].Values, diskInfo.Name) + fields[1].Values = append(fields[1].Values, diskInfo.Model) + fields[2].Values = append(fields[2].Values, diskInfo.Size) + fields[3].Values = append(fields[3].Values, diskInfo.MountPoint) + fields[4].Values = append(fields[4].Values, diskInfo.Type) + fields[5].Values = append(fields[5].Values, diskInfo.RequestQueueSize) + fields[6].Values = append(fields[6].Values, diskInfo.MinIOSize) + fields[7].Values = append(fields[7].Values, diskInfo.FirmwareVersion) + fields[8].Values = append(fields[8].Values, diskInfo.PCIeAddress) + fields[9].Values = append(fields[9].Values, diskInfo.NUMANode) + fields[10].Values = append(fields[10].Values, diskInfo.LinkSpeed) + fields[11].Values = append(fields[11].Values, diskInfo.LinkWidth) + fields[12].Values = append(fields[12].Values, diskInfo.MaxLinkSpeed) + fields[13].Values = append(fields[13].Values, diskInfo.MaxLinkWidth) + } + return fields +} + +func filesystemTableValues(outputs map[string]script.ScriptOutput) []Field { + return filesystemFieldValuesFromOutput(outputs) +} + +func filesystemTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + mountOptionsIndex, err := getFieldIndex("Mount Options", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + for i, options := range tableValues.Fields[mountOptionsIndex].Values { + if strings.Contains(options, "discard") { + insights = append(insights, Insight{ + Recommendation: fmt.Sprintf("Consider mounting the '%s' file system without the 'discard' option and instead configure periodic TRIM for SSDs, if used for I/O intensive workloads.", tableValues.Fields[0].Values[i]), + Justification: fmt.Sprintf("The '%s' filesystem is mounted with 'discard' option.", tableValues.Fields[0].Values[i]), + }) + } + } + } + return insights +} + +func gpuTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Manufacturer"}, + {Name: "Model"}, + {Name: "PCI ID"}, + } + gpuInfos := gpuInfoFromOutput(outputs) + for _, gpuInfo := range gpuInfos { + fields[0].Values = append(fields[0].Values, gpuInfo.Manufacturer) + fields[1].Values = append(fields[1].Values, gpuInfo.Model) + fields[2].Values = append(fields[2].Values, gpuInfo.PCIID) + } + return fields +} + +func gaudiTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Module ID"}, + {Name: "Serial Number"}, + {Name: "Bus ID"}, + {Name: "Driver Version"}, + {Name: "EROM"}, + {Name: "CPLD"}, + {Name: "SPI"}, + {Name: "NUMA"}, + } + gaudiInfos := gaudiInfoFromOutput(outputs) + for _, gaudiInfo := range gaudiInfos { + fields[0].Values = append(fields[0].Values, gaudiInfo.ModuleID) + fields[1].Values = append(fields[1].Values, gaudiInfo.SerialNumber) + fields[2].Values = append(fields[2].Values, gaudiInfo.BusID) + fields[3].Values = append(fields[3].Values, gaudiInfo.DriverVersion) + fields[4].Values = append(fields[4].Values, gaudiInfo.EROM) + fields[5].Values = append(fields[5].Values, gaudiInfo.CPLD) + fields[6].Values = append(fields[6].Values, gaudiInfo.SPI) + fields[7].Values = append(fields[7].Values, gaudiInfo.NUMA) + } + return fields +} + +func cxlDeviceTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Slot"}, + {Name: "Class"}, + {Name: "Vendor"}, + {Name: "Device"}, + {Name: "Rev"}, + {Name: "ProgIf"}, + {Name: "NUMANode"}, + {Name: "IOMMUGroup"}, + } + cxlDevices := getPCIDevices("CXL", outputs) + for _, cxlDevice := range cxlDevices { + for _, field := range fields { + if value, ok := cxlDevice[field.Name]; ok { + field.Values = append(field.Values, value) + } else { + field.Values = append(field.Values, "") + } + } + } + return fields +} + +func cveTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{} + cves := cveInfoFromOutput(outputs) + for _, cve := range cves { + fields = append(fields, Field{Name: cve[0], Values: []string{cve[1]}}) + } + return fields +} + +func cveTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + for _, field := range tableValues.Fields { + if strings.HasPrefix(field.Values[0], "VULN") { + insights = append(insights, Insight{ + Recommendation: fmt.Sprintf("Consider applying the security patch for %s.", field.Name), + Justification: fmt.Sprintf("The system is vulnerable to %s.", field.Name), + }) + } + } + return insights +} + +func processTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{} + for i, line := range strings.Split(outputs[script.ProcessListScriptName].Stdout, "\n") { + tokens := strings.Fields(line) + if i == 0 { // header -- defines fields in table + for _, token := range tokens { + fields = append(fields, Field{Name: token}) + } + continue + } + // combine trailing fields + if len(tokens) > len(fields) { + tokens[len(fields)-1] = strings.Join(tokens[len(fields)-1:], " ") + tokens = tokens[:len(fields)] + } + for j, token := range tokens { + fields[j].Values = append(fields[j].Values, token) + } + } + return fields +} + +func sensorTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Sensor"}, + {Name: "Reading"}, + {Name: "Status"}, + } + for _, line := range strings.Split(outputs[script.IpmitoolSensorsScriptName].Stdout, "\n") { + tokens := strings.Split(line, " | ") + if len(tokens) < len(fields) { + continue + } + fields[0].Values = append(fields[0].Values, tokens[0]) + fields[1].Values = append(fields[1].Values, tokens[1]) + fields[2].Values = append(fields[2].Values, tokens[2]) + } + return fields +} + +func chassisStatusTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Last Power Event", Values: []string{valFromRegexSubmatch(outputs[script.IpmitoolChassisScriptName].Stdout, `^Last Power Event\s*: (.+?)$`)}}, + {Name: "Power Overload", Values: []string{valFromRegexSubmatch(outputs[script.IpmitoolChassisScriptName].Stdout, `^Power Overload\s*: (.+?)$`)}}, + {Name: "Main Power Fault", Values: []string{valFromRegexSubmatch(outputs[script.IpmitoolChassisScriptName].Stdout, `^Main Power Fault\s*: (.+?)$`)}}, + {Name: "Power Restore Policy", Values: []string{valFromRegexSubmatch(outputs[script.IpmitoolChassisScriptName].Stdout, `^Power Restore Policy\s*: (.+?)$`)}}, + {Name: "Drive Fault", Values: []string{valFromRegexSubmatch(outputs[script.IpmitoolChassisScriptName].Stdout, `^Drive Fault\s*: (.+?)$`)}}, + {Name: "Cooling/Fan Fault", Values: []string{valFromRegexSubmatch(outputs[script.IpmitoolChassisScriptName].Stdout, `^Cooling/Fan Fault\s*: (.+?)$`)}}, + {Name: "System Time", Values: []string{strings.TrimSpace(outputs[script.IpmitoolEventTimeScriptName].Stdout)}}, + } +} + +func systemEventLogTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Date"}, + {Name: "Time"}, + {Name: "Sensor"}, + {Name: "Status"}, + {Name: "Event"}, + } + for _, line := range strings.Split(outputs[script.IpmitoolEventsScriptName].Stdout, "\n") { + tokens := strings.Split(line, " | ") + if len(tokens) < len(fields) { + continue + } + fields[0].Values = append(fields[0].Values, tokens[0]) + fields[1].Values = append(fields[1].Values, tokens[1]) + fields[2].Values = append(fields[2].Values, tokens[2]) + fields[3].Values = append(fields[3].Values, tokens[3]) + fields[4].Values = append(fields[4].Values, tokens[4]) + } + return fields +} + +func systemEventLogTableInsights(outputs map[string]script.ScriptOutput, tableValues TableValues) []Insight { + insights := []Insight{} + sensorFieldIndex, err := getFieldIndex("Sensor", tableValues) + if err != nil { + slog.Warn(err.Error()) + } else { + temperatureEvents := 0 + for _, sensor := range tableValues.Fields[sensorFieldIndex].Values { + if strings.Contains(sensor, "Temperature") { + temperatureEvents++ + } + } + if temperatureEvents > 0 { + insights = append(insights, Insight{ + Recommendation: "Consider reviewing the System Event Log table.", + Justification: fmt.Sprintf("Detected '%d' temperature-related service action(s) in the System Event Log.", temperatureEvents), + }) + } + } + return insights +} + +func kernelLogTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Entries", Values: strings.Split(outputs[script.KernelLogScriptName].Stdout, "\n")}, + } +} + +func pmuTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "PMU Driver Version", Values: []string{strings.TrimSpace(outputs[script.PMUDriverVersionScriptName].Stdout)}}, + {Name: "cpu_cycles", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0x30a (.*)$`)}}, + {Name: "instructions", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0x309 (.*)$`)}}, + {Name: "ref_cycles", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0x30b (.*)$`)}}, + {Name: "topdown_slots", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0x30c (.*)$`)}}, + {Name: "gen_programmable_1", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc1 (.*)$`)}}, + {Name: "gen_programmable_2", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc2 (.*)$`)}}, + {Name: "gen_programmable_3", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc3 (.*)$`)}}, + {Name: "gen_programmable_4", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc4 (.*)$`)}}, + {Name: "gen_programmable_5", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc5 (.*)$`)}}, + {Name: "gen_programmable_6", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc6 (.*)$`)}}, + {Name: "gen_programmable_7", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc7 (.*)$`)}}, + {Name: "gen_programmable_8", Values: []string{valFromRegexSubmatch(outputs[script.PMUBusyScriptName].Stdout, `^0xc8 (.*)$`)}}, + } +} + +func systemSummaryTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Host Name", Values: []string{strings.TrimSpace(outputs[script.HostnameScriptName].Stdout)}}, + {Name: "Time", Values: []string{strings.TrimSpace(outputs[script.DateScriptName].Stdout)}}, + {Name: "System", Values: []string{valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "1", `^Manufacturer:\s*(.+?)$`) + " " + valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "1", `^Product Name:\s*(.+?)$`)}}, + {Name: "Baseboard", Values: []string{valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "2", `^Manufacturer:\s*(.+?)$`) + " " + valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "2", `^Product Name:\s*(.+?)$`)}}, + {Name: "Chassis", Values: []string{valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "3", `^Manufacturer:\s*(.+?)$`) + " " + valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "3", `^Type:\s*(.+?)$`)}}, + {Name: "CPU Model", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^[Mm]odel name:\s*(.+)$`)}}, + {Name: "Architecture", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Architecture:\s*(.+)$`)}}, + {Name: "Microarchitecture", Values: []string{uarchFromOutput(outputs)}}, + {Name: "Cores per Socket", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(.+)$`)}}, + {Name: "Sockets", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(s\):\s*(.+)$`)}}, + {Name: "Hyperthreading", Values: []string{hyperthreadingFromOutput(outputs)}}, + {Name: "CPUs", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU\(s\):\s*(.+)$`)}}, + {Name: "Intel Turbo Boost", Values: []string{turboEnabledFromOutput(outputs)}}, + {Name: "Base Frequency", Values: []string{baseFrequencyFromOutput(outputs)}}, + {Name: "All-core Maximum Frequency", Values: []string{allCoreMaxFrequencyFromOutput(outputs)}}, + {Name: "Maximum Frequency", Values: []string{maxFrequencyFromOutput(outputs)}}, + {Name: "NUMA Nodes", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^NUMA node\(s\):\s*(.+)$`)}}, + {Name: "Prefetchers", Values: []string{prefetchersFromOutput(outputs)}}, + {Name: "PPINs", Values: []string{ppinsFromOutput(outputs)}}, + {Name: "Accelerators Available [used]", Values: []string{acceleratorSummaryFromOutput(outputs)}}, + {Name: "Installed Memory", Values: []string{installedMemoryFromOutput(outputs)}}, + {Name: "Hugepagesize", Values: []string{valFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^Hugepagesize:\s*(.+?)$`)}}, + {Name: "Transparent Huge Pages", Values: []string{valFromRegexSubmatch(outputs[script.TransparentHugePagesScriptName].Stdout, `.*\[(.*)\].*`)}}, + {Name: "Automatic NUMA Balancing", Values: []string{numaBalancingFromOutput(outputs)}}, + {Name: "NIC", Values: []string{nicSummaryFromOutput(outputs)}}, + {Name: "Disk", Values: []string{diskSummaryFromOutput(outputs)}}, + {Name: "BIOS", Values: []string{valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "0", `^Version:\s*(.+?)$`)}}, + {Name: "Microcode", Values: []string{valFromRegexSubmatch(outputs[script.ProcCpuinfoScriptName].Stdout, `^microcode.*:\s*(.+?)$`)}}, + {Name: "OS", Values: []string{operatingSystemFromOutput(outputs)}}, + {Name: "Kernel", Values: []string{valFromRegexSubmatch(outputs[script.UnameScriptName].Stdout, `^Linux \S+ (\S+)`)}}, + {Name: "TDP", Values: []string{tdpFromOutput(outputs)}}, + {Name: "Energy Performance Bias", Values: []string{epbFromOutput(outputs)}}, + {Name: "Scaling Governor", Values: []string{strings.TrimSpace(outputs[script.ScalingGovernorScriptName].Stdout)}}, + {Name: "Scaling Driver", Values: []string{strings.TrimSpace(outputs[script.ScalingDriverScriptName].Stdout)}}, + {Name: "C-states", Values: []string{cstatesSummaryFromOutput(outputs)}}, + {Name: "Efficiency Latency Control", Values: []string{elcSummaryFromOutput(outputs)}}, + {Name: "CVEs", Values: []string{cveSummaryFromOutput(outputs)}}, + {Name: "System Summary", Values: []string{systemSummaryFromOutput(outputs)}}, + } +} + +func configurationTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Cores per Socket", Values: []string{valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(.+)$`)}}, + {Name: "L3 Cache", Values: []string{l3FromOutput(outputs)}}, + {Name: "Package Power / TDP (Watts)", Values: []string{tdpFromOutput(outputs)}}, + {Name: "All-Core Max Frequency (GHz)", Values: []string{allCoreMaxFrequencyFromOutput(outputs)}}, + {Name: "Uncore Max Frequency (GHz)", Values: []string{uncoreMaxFrequencyFromOutput(outputs)}}, + {Name: "Uncore Min Frequency (GHz)", Values: []string{uncoreMinFrequencyFromOutput(outputs)}}, + {Name: "Energy Performance Bias", Values: []string{epbFromOutput(outputs)}}, + {Name: "Energy Performance Preference", Values: []string{eppFromOutput(outputs)}}, + {Name: "Scaling Governor", Values: []string{strings.TrimSpace(outputs[script.ScalingGovernorScriptName].Stdout)}}, + {Name: "Efficiency Latency Control", Values: []string{elcSummaryFromOutput(outputs)}}, + } +} + +// benchmarking + +func cpuSpeedTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Ops/s", Values: []string{cpuSpeedFromOutput(outputs)}}, + } +} + +func cpuPowerTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Maximum Power", Values: []string{maxPowerFromOutput(outputs)}}, + {Name: "Minimum Power", Values: []string{minPowerFromOutput(outputs)}}, + } +} + +func cpuTemperatureTableValues(outputs map[string]script.ScriptOutput) []Field { + return []Field{ + {Name: "Maximum Temperature", Values: []string{maxTemperatureFromOutput(outputs)}}, + } +} + +func cpuFrequencyTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := coreTurboFrequencyTableValues(outputs) // may result in no values within the two fields + if len(fields) != 2 { + panic("coreTurboFrequencyTableValues must return 2 fields") + } + fields = append(fields, []Field{ + {Name: "non-avx"}, + {Name: "avx128"}, + {Name: "avx256"}, + {Name: "avx512"}, + }...) + nonavxFreqs, avx128Freqs, avx256Freqs, avx512Freqs, err := avxTurboFrequenciesFromOutput(outputs[script.TurboFrequenciesScriptName].Stdout) + if err != nil { + slog.Warn(err.Error()) + fields[0].Values = []string{} + fields[1].Values = []string{} + return fields + } else { + // pad core counts and spec frequency (field 0 and 1) + for i := 0; i < 2; i++ { + for j := len(fields[i].Values); j < len(nonavxFreqs); j++ { + pad := "" + if i == 0 { + pad = strconv.Itoa(j + 1) + } + fields[i].Values = append(fields[i].Values, pad) + } + } + for i := 0; i < len(nonavxFreqs); i++ { + fields[2].Values = append(fields[2].Values, fmt.Sprintf("%.1f", nonavxFreqs[i])) + } + for i := 0; i < len(avx128Freqs); i++ { + fields[3].Values = append(fields[3].Values, fmt.Sprintf("%.1f", avx128Freqs[i])) + } + for i := 0; i < len(avx256Freqs); i++ { + fields[4].Values = append(fields[4].Values, fmt.Sprintf("%.1f", avx256Freqs[i])) + } + for i := 0; i < len(avx512Freqs); i++ { + fields[5].Values = append(fields[5].Values, fmt.Sprintf("%.1f", avx512Freqs[i])) + } + } + // pad frequency fields with empty string + for i := 2; i < len(fields); i++ { + for j := len(fields[i].Values); j < len(fields[0].Values); j++ { + fields[i].Values = append(fields[i].Values, "") + } + } + return fields +} + +func memoryLatencyTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Latency (ns)"}, + {Name: "Bandwidth (GB/s)"}, + } + /* MLC Output: + Inject Latency Bandwidth + Delay (ns) MB/sec + ========================== + 00000 261.65 225060.9 + 00002 261.63 225040.5 + 00008 261.54 225073.3 + ... + */ + latencyBandwidthPairs := valsArrayFromRegexSubmatch(outputs[script.MemoryBandwidthAndLatencyScriptName].Stdout, `\s*[0-9]*\s*([0-9]*\.[0-9]+)\s*([0-9]*\.[0-9]+)`) + for _, latencyBandwidth := range latencyBandwidthPairs { + latency := latencyBandwidth[0] + bandwidth, err := strconv.ParseFloat(latencyBandwidth[1], 32) + if err != nil { + slog.Error(fmt.Sprintf("Unable to convert bandwidth to float: %s", latencyBandwidth[1])) + continue + } + // insert into beginning of list + fields[0].Values = append([]string{latency}, fields[0].Values...) + fields[1].Values = append([]string{fmt.Sprintf("%.1f", bandwidth/1000)}, fields[1].Values...) + } + return fields +} + +func numaBandwidthTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Node"}, + } + /* MLC Output: + Numa node + Numa node 0 1 + 0 175610.3 55579.7 + 1 55575.2 175656.7 + */ + nodeBandwidthsPairs := valsArrayFromRegexSubmatch(outputs[script.NumaBandwidthScriptName].Stdout, `^\s+(\d)\s+(\d.*)$`) + // add 1 field per numa node + for _, nodeBandwidthsPair := range nodeBandwidthsPairs { + fields = append(fields, Field{Name: nodeBandwidthsPair[0]}) + } + // add rows + for _, nodeBandwidthsPair := range nodeBandwidthsPairs { + fields[0].Values = append(fields[0].Values, nodeBandwidthsPair[0]) + bandwidths := strings.Split(strings.TrimSpace(nodeBandwidthsPair[1]), "\t") + if len(bandwidths) != len(nodeBandwidthsPairs) { + slog.Warn(fmt.Sprintf("Mismatched number of bandwidths for numa node %s, %s", nodeBandwidthsPair[0], nodeBandwidthsPair[1])) + return []Field{} + } + for i, bw := range bandwidths { + val, err := strconv.ParseFloat(bw, 64) + if err != nil { + slog.Error(fmt.Sprintf("Unable to convert bandwidth to float: %s", bw)) + continue + } + fields[i+1].Values = append(fields[i+1].Values, fmt.Sprintf("%.1f", val/1000)) + } + } + return fields +} + +// telemetry + +func cpuUtilizationTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Time"}, + {Name: "CPU"}, + {Name: "CORE"}, + {Name: "SOCK"}, + {Name: "NODE"}, + {Name: "%usr"}, + {Name: "%nice"}, + {Name: "%sys"}, + {Name: "%iowait"}, + {Name: "%irq"}, + {Name: "%soft"}, + {Name: "%steal"}, + {Name: "%guest"}, + {Name: "%gnice"}, + {Name: "%idle"}, + } + reStat := regexp.MustCompile(`^(\d\d:\d\d:\d\d)\s+(\d+)\s+(\d+)\s+(\d+)\s+(-*\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)$`) + for _, line := range strings.Split(outputs[script.MpstatScriptName].Stdout, "\n") { + match := reStat.FindStringSubmatch(line) + if len(match) == 0 { + continue + } + for i := range fields { + fields[i].Values = append(fields[i].Values, match[i+1]) + } + } + return fields +} + +func averageCPUUtilizationTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Time"}, + {Name: "%usr"}, + {Name: "%nice"}, + {Name: "%sys"}, + {Name: "%iowait"}, + {Name: "%irq"}, + {Name: "%soft"}, + {Name: "%steal"}, + {Name: "%guest"}, + {Name: "%gnice"}, + {Name: "%idle"}, + } + reStat := regexp.MustCompile(`^(\d\d:\d\d:\d\d)\s+all\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)$`) + for _, line := range strings.Split(outputs[script.MpstatScriptName].Stdout, "\n") { + match := reStat.FindStringSubmatch(line) + if len(match) == 0 { + continue + } + for i := range fields { + fields[i].Values = append(fields[i].Values, match[i+1]) + } + } + return fields +} + +func irqRateTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Time"}, + {Name: "CPU"}, + {Name: "HI/s"}, + {Name: "TIMER/s"}, + {Name: "NET_TX/s"}, + {Name: "NET_RX/s"}, + {Name: "BLOCK/s"}, + {Name: "IRQ_POLL/s"}, + {Name: "TASKLET/s"}, + {Name: "SCHED/s"}, + {Name: "HRTIMER/s"}, + {Name: "RCU/s"}, + } + reStat := regexp.MustCompile(`^(\d\d:\d\d:\d\d)\s+(\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)$`) + for _, line := range strings.Split(outputs[script.MpstatScriptName].Stdout, "\n") { + match := reStat.FindStringSubmatch(line) + if len(match) == 0 { + continue + } + for i := range fields { + fields[i].Values = append(fields[i].Values, match[i+1]) + } + } + return fields +} + +func driveStatsTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Time"}, + {Name: "Device"}, + {Name: "tps"}, + {Name: "kB_read/s"}, + {Name: "kB_wrtn/s"}, + {Name: "kB_dscd/s"}, + } + // the time is on its own line, so we need to keep track of it + reTime := regexp.MustCompile(`^\d\d\d\d-\d\d-\d\dT(\d\d:\d\d:\d\d)`) + // don't capture the last three vals: "kB_read","kB_wrtn","kB_dscd" -- they aren't the same scale as the others + reStat := regexp.MustCompile(`^(\w+)\s*(\d+.\d+)\s*(\d+.\d+)\s*(\d+.\d+)\s*(\d+.\d+)\s*\d+\s*\d+\s*\d+$`) + var time string + for _, line := range strings.Split(outputs[script.IostatScriptName].Stdout, "\n") { + match := reTime.FindStringSubmatch(line) + if len(match) > 0 { + time = match[1] + continue + } + match = reStat.FindStringSubmatch(line) + if len(match) > 0 { + fields[0].Values = append(fields[0].Values, time) + for i := range fields[1:] { + fields[i+1].Values = append(fields[i+1].Values, match[i+1]) + } + } + } + return fields +} + +func networkStatsTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Time"}, + {Name: "IFACE"}, + {Name: "rxpck/s"}, + {Name: "txpck/s"}, + {Name: "rxkB/s"}, + {Name: "txkB/s"}, + } + // don't capture the last four vals: "rxcmp/s","txcmp/s","rxcmt/s","%ifutil" -- obscure more important vals + reStat := regexp.MustCompile(`^(\d+:\d+:\d+)\s*(\w*)\s*(\d+.\d+)\s*(\d+.\d+)\s*(\d+.\d+)\s*(\d+.\d+)\s*\d+.\d+\s*\d+.\d+\s*\d+.\d+\s*\d+.\d+$`) + for _, line := range strings.Split(outputs[script.SarNetworkScriptName].Stdout, "\n") { + match := reStat.FindStringSubmatch(line) + if len(match) == 0 { + continue + } + for i := range fields { + fields[i].Values = append(fields[i].Values, match[i+1]) + } + } + return fields +} + +func memoryStatsTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Time"}, + {Name: "free"}, + {Name: "avail"}, + {Name: "used"}, + {Name: "buffers"}, + {Name: "cache"}, + {Name: "commit"}, + {Name: "active"}, + {Name: "inactive"}, + {Name: "dirty"}, + } + reStat := regexp.MustCompile(`^(\d+:\d+:\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*\d+\.\d+\s*(\d+)\s*(\d+)\s*(\d+)\s*\d+\.\d+\s*(\d+)\s*(\d+)\s*(\d+)$`) + for _, line := range strings.Split(outputs[script.SarMemoryScriptName].Stdout, "\n") { + match := reStat.FindStringSubmatch(line) + if len(match) == 0 { + continue + } + for i := range fields { + fields[i].Values = append(fields[i].Values, match[i+1]) + } + } + return fields +} + +func powerStatsTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "Time"}, + {Name: "Package"}, + {Name: "DRAM"}, + } + reStat := regexp.MustCompile(`^(\d\d:\d\d:\d\d)\s*(\d+\.\d+)\s*(\d+\.\d+)$`) + for _, line := range strings.Split(outputs[script.TurbostatScriptName].Stdout, "\n") { + match := reStat.FindStringSubmatch(line) + if len(match) == 0 { + continue + } + for i := range fields { + fields[i].Values = append(fields[i].Values, match[i+1]) + } + } + return fields +} + +func codePathFrequencyTableValues(outputs map[string]script.ScriptOutput) []Field { + fields := []Field{ + {Name: "System Paths", Values: []string{systemFoldedFromOutput(outputs)}}, + {Name: "Java Paths", Values: []string{javaFoldedFromOutput(outputs)}}, + } + return fields +} diff --git a/internal/report/table_helpers.go b/internal/report/table_helpers.go new file mode 100644 index 0000000..f1687b7 --- /dev/null +++ b/internal/report/table_helpers.go @@ -0,0 +1,1743 @@ +package report + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// table_helpers.go contains helper functions that are used to extract values from the output of the scripts. + +import ( + "encoding/csv" + "fmt" + "log" + "log/slog" + "math" + "regexp" + "sort" + "strconv" + "strings" + + "perfspect/internal/cpudb" + "perfspect/internal/script" +) + +// valFromRegexSubmatch searches for a regex pattern in the given output string and returns the first captured group. +// If no match is found, an empty string is returned. +func valFromRegexSubmatch(output string, regex string) string { + re := regexp.MustCompile(regex) + for _, line := range strings.Split(output, "\n") { + match := re.FindStringSubmatch(strings.TrimSpace(line)) + if len(match) > 1 { + return match[1] + } + } + return "" +} + +// valsFromRegexSubmatch extracts the captured groups from each line in the output +// that matches the given regular expression. +// It returns a slice of strings containing the captured values. +func valsFromRegexSubmatch(output string, regex string) []string { + var vals []string + re := regexp.MustCompile(regex) + for _, line := range strings.Split(output, "\n") { + match := re.FindStringSubmatch(strings.TrimSpace(line)) + if len(match) > 1 { + vals = append(vals, match[1]) + } + } + return vals +} + +// return all matches for all capture groups in regex +func valsArrayFromRegexSubmatch(output string, regex string) (vals [][]string) { + re := regexp.MustCompile(regex) + for _, line := range strings.Split(output, "\n") { + match := re.FindStringSubmatch(line) + if len(match) > 1 { + vals = append(vals, match[1:]) + } + } + return +} + +// valFromDmiDecodeRegexSubmatch extracts a value from the DMI decode output using a regular expression. +// It takes the DMI decode output, the DMI type, and the regular expression as input parameters. +// It returns the extracted value as a string. +func valFromDmiDecodeRegexSubmatch(dmiDecodeOutput string, dmiType string, regex string) string { + return valFromRegexSubmatch(getDmiDecodeType(dmiDecodeOutput, dmiType), regex) +} + +func valsArrayFromDmiDecodeRegexSubmatch(dmiDecodeOutput string, dmiType string, regexes ...string) (vals [][]string) { + var res []*regexp.Regexp + for _, r := range regexes { + re := regexp.MustCompile(r) + res = append(res, re) + } + for _, entry := range getDmiDecodeEntries(dmiDecodeOutput, dmiType) { + row := make([]string, len(res)) + for _, line := range entry { + for i, re := range res { + match := re.FindStringSubmatch(strings.TrimSpace(line)) + if len(match) > 1 { + row[i] = match[1] + } + } + } + vals = append(vals, row) + } + return +} + +// getDmiDecodeType extracts the lines from the given `dmiDecodeOutput` that belong to the specified `dmiType`. +func getDmiDecodeType(dmiDecodeOutput string, dmiType string) string { + var lines []string + start := false + for _, line := range strings.Split(dmiDecodeOutput, "\n") { + if start && strings.HasPrefix(line, "Handle ") { + start = false + } + if strings.Contains(line, "DMI type "+dmiType+",") { + start = true + } + if start { + lines = append(lines, line) + } + } + return strings.Join(lines, "\n") +} + +// getDmiDecodeEntries extracts the entries from the given `dmiDecodeOutput` that belong to the specified `dmiType`. +func getDmiDecodeEntries(dmiDecodeOutput string, dmiType string) (entries [][]string) { + lines := strings.Split(dmiDecodeOutput, "\n") + var entry []string + typeMatch := false + for _, line := range lines { + if strings.HasPrefix(line, "Handle ") { + if strings.Contains(line, "DMI type "+dmiType+",") { + // type match + typeMatch = true + entry = []string{} + } else { + // not a type match + typeMatch = false + } + } + if !typeMatch { + continue + } + if line == "" { + // end of type match entry + entries = append(entries, entry) + } else { + // a line in the entry + entry = append(entry, line) + } + } + return +} + +// uarchFromOutput returns the architecture of the CPU that matches family, model, stepping, sockets, +// capid4, and devices information from the output or an empty string, if no match is found. +func uarchFromOutput(outputs map[string]script.ScriptOutput) string { + family := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU family:\s*(.+)$`) + model := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Model:\s*(.+)$`) + stepping := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Stepping:\s*(.+)$`) + sockets := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(s\):\s*(.+)$`) + capid4 := valFromRegexSubmatch(outputs[script.LspciBitsScriptName].Stdout, `^([0-9a-fA-F]+)`) + devices := valFromRegexSubmatch(outputs[script.LspciDevicesScriptName].Stdout, `^([0-9]+)`) + CPUdb := cpudb.NewCPUDB() + cpu, err := CPUdb.GetCPU(family, model, stepping, capid4, sockets, devices) + if err == nil { + return cpu.MicroArchitecture + } + return "" +} + +// UarchFromOutput exports the uarchFromOutput function +func UarchFromOutput(outputs map[string]script.ScriptOutput) string { + return uarchFromOutput(outputs) +} + +// baseFrequencyFromOutput gets base core frequency +// +// 1st option) /sys/devices/system/cpu/cpu0/cpufreq/base_frequency +// 2nd option) from dmidecode "Current Speed" +// 3nd option) parse it from the model name +func baseFrequencyFromOutput(outputs map[string]script.ScriptOutput) string { + cmdout := strings.TrimSpace(outputs[script.BaseFrequencyScriptName].Stdout) + if cmdout != "" { + freqf, err := strconv.ParseFloat(cmdout, 64) + if err == nil { + freqf = freqf / 1000000 + return fmt.Sprintf("%.1fGHz", freqf) + } + } + currentSpeedVal := valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "4", `Current Speed:\s(.*)$`) + tokens := strings.Split(currentSpeedVal, " ") + if len(tokens) == 2 { + num, err := strconv.ParseFloat(tokens[0], 64) + if err == nil { + unit := tokens[1] + if unit == "MHz" { + num = num / 1000 + unit = "GHz" + } + return fmt.Sprintf("%.1f%s", num, unit) + } + } + // the frequency (if included) is at the end of the model name in lscpu's output + modelName := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^[Mm]odel name.*:\s*(.+?)$`) + tokens = strings.Split(modelName, " ") + if len(tokens) > 0 { + lastToken := tokens[len(tokens)-1] + if len(lastToken) > 0 && lastToken[len(lastToken)-1] == 'z' { + return lastToken + } + } + return "" +} + +func convertMsrToDecimals(msr string) (decVals []int64, err error) { + re := regexp.MustCompile(`[0-9a-fA-F][0-9a-fA-F]`) + hexVals := re.FindAll([]byte(msr), -1) + if hexVals == nil { + err = fmt.Errorf("no hex values found in msr") + return + } + decVals = make([]int64, len(hexVals)) + decValsIndex := len(decVals) - 1 + for _, hexVal := range hexVals { + var decVal int64 + decVal, err = strconv.ParseInt(string(hexVal), 16, 64) + if err != nil { + return + } + decVals[decValsIndex] = decVal + decValsIndex-- + } + return +} + +func getSpecCountFrequencies(outputs map[string]script.ScriptOutput) (countFreqs [][]string, err error) { + hexCounts := valFromRegexSubmatch(outputs[script.SpecTurboCoresScriptName].Stdout, `^([0-9a-fA-F]+)`) + hexFreqs := valFromRegexSubmatch(outputs[script.SpecTurboFrequenciesScriptName].Stdout, `^([0-9a-fA-F]+)`) + if hexCounts == "" || hexFreqs == "" { + err = fmt.Errorf("no hex counts or frequencies found") + return + } + var decCounts, decFreqs []int64 + decCounts, err = convertMsrToDecimals(hexCounts) + if err != nil { + return + } + uarch := uarchFromOutput(outputs) + if strings.Contains(uarch, "SRF") { + for i, count := range decCounts[:] { + decCounts[i] = count * 4 // 4 cores per count + } + } + decFreqs, err = convertMsrToDecimals(hexFreqs) + if err != nil { + return + } + if len(decCounts) != 8 || len(decFreqs) != 8 { + err = fmt.Errorf("unexpected number of core counts or frequencies") + return + } + for i, decCount := range decCounts { + countFreqs = append(countFreqs, []string{fmt.Sprintf("%d", decCount), fmt.Sprintf("%.1f", float64(decFreqs[i])/10.0)}) + } + return +} + +// maxFrequencyFromOutputs gets max core frequency +// +// 1st option) /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq +// 2nd option) from MSR +// 3rd option) from dmidecode "Max Speed" +func maxFrequencyFromOutput(outputs map[string]script.ScriptOutput) string { + cmdout := strings.TrimSpace(outputs[script.MaximumFrequencyScriptName].Stdout) + if cmdout != "" { + freqf, err := strconv.ParseFloat(cmdout, 64) + if err == nil { + freqf = freqf / 1000000 + return fmt.Sprintf("%.1fGHz", freqf) + } + } + countFreqs, err := getSpecCountFrequencies(outputs) + // the first entry is the max single-core frequency + if err == nil && len(countFreqs) > 0 && len(countFreqs[0]) > 1 { + return countFreqs[0][1] + } + + return valFromDmiDecodeRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, "4", `Max Speed:\s(.*)`) +} + +func allCoreMaxFrequencyFromOutput(outputs map[string]script.ScriptOutput) string { + countFreqs, err := getSpecCountFrequencies(outputs) + // the last entry is the max all-core frequency + if err == nil && len(countFreqs) > 0 && len(countFreqs[len(countFreqs)-1]) > 1 { + return countFreqs[len(countFreqs)-1][1] + "GHz" + } + return "" +} + +func hyperthreadingFromOutput(outputs map[string]script.ScriptOutput) string { + family := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU family:\s*(.+)$`) + model := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Model:\s*(.+)$`) + stepping := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Stepping:\s*(.+)$`) + sockets := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(s\):\s*(.+)$`) + coresPerSocket := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(.+)$`) + cpus := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU\(.*:\s*(.+?)$`) + numCPUs, err := strconv.Atoi(cpus) // logical CPUs + if err != nil { + slog.Error("error parsing cpus from lscpu") + return "" + } + numSockets, err := strconv.Atoi(sockets) + if err != nil { + slog.Error("error parsing sockets from lscpu") + return "" + } + numCoresPerSocket, err := strconv.Atoi(coresPerSocket) // physical cores + if err != nil { + slog.Error("error parsing cores per sockets from lscpu") + return "" + } + CPUdb := cpudb.NewCPUDB() + cpu, err := CPUdb.GetCPU(family, model, stepping, "", sockets, "") + if err != nil { + return "" + } + if cpu.LogicalThreadCount < 2 { + return "N/A" + } else if numCPUs > numCoresPerSocket*numSockets { + return "Enabled" + } else { + return "Disabled" + } +} + +func numaCPUListFromOutput(outputs map[string]script.ScriptOutput) string { + nodeCPUs := valsFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^NUMA node[0-9] CPU\(.*:\s*(.+?)$`) + return strings.Join(nodeCPUs, " :: ") +} + +func ppinsFromOutput(outputs map[string]script.ScriptOutput) string { + uniquePpins := []string{} + for _, line := range strings.Split(outputs[script.PPINName].Stdout, "\n") { + parts := strings.Split(line, ":") + if len(parts) < 2 { + continue + } + ppin := strings.TrimSpace(parts[1]) + found := false + for _, p := range uniquePpins { + if string(p) == ppin { + found = true + break + } + } + if !found && ppin != "" { + uniquePpins = append(uniquePpins, ppin) + } + } + return strings.Join(uniquePpins, ", ") +} + +func channelsFromOutput(outputs map[string]script.ScriptOutput) string { + family := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU family:\s*(.+)$`) + model := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Model:\s*(.+)$`) + stepping := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Stepping:\s*(.+)$`) + sockets := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(.*:\s*(.+?)$`) + capid4 := valFromRegexSubmatch(outputs[script.LspciBitsScriptName].Stdout, `^([0-9a-fA-F]+)`) + devices := valFromRegexSubmatch(outputs[script.LspciDevicesScriptName].Stdout, `^([0-9]+)`) + CPUdb := cpudb.NewCPUDB() + cpu, err := CPUdb.GetCPU(family, model, stepping, capid4, sockets, devices) + if err != nil { + slog.Error("error getting CPU from CPUdb", slog.String("error", err.Error())) + return "" + } + return fmt.Sprintf("%d", cpu.MemoryChannelCount) +} + +func turboEnabledFromOutput(outputs map[string]script.ScriptOutput) string { + family := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^CPU family:\s*(.+)$`) + if family == "6" { // Intel + val := valFromRegexSubmatch(outputs[script.CpuidScriptName].Stdout, `^Intel Turbo Boost Technology\s*= (.+?)$`) + if val == "true" { + return "Enabled" + } + if val == "false" { + return "Disabled" + } + return "" // unknown value + } else if family == "23" || family == "25" { // AMD + val := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Frequency boost.*:\s*(.+?)$`) + if val != "" { + return val + " (AMD Frequency Boost)" + } + } + return "" +} + +func prefetchersFromOutput(outputs map[string]script.ScriptOutput) string { + uarch := uarchFromOutput(outputs) + if uarch == "" { + // uarch is required + return "" + } + // MSR_PREFETCH_CONTROL + // prefetchers are enabled when associated bit is 0 + prefetcherDefs := []struct { + name string + msr string + bit int + uarchs string + }{ + {name: "L2 HW", msr: script.PrefetchControlName, bit: 0, uarchs: "all"}, + {name: "L2 Adj.", msr: script.PrefetchControlName, bit: 1, uarchs: "all"}, + {name: "DCU HW", msr: script.PrefetchControlName, bit: 2, uarchs: "all"}, + {name: "DCU IP", msr: script.PrefetchControlName, bit: 3, uarchs: "all"}, + {name: "AMP", msr: script.PrefetchControlName, bit: 5, uarchs: "SPR_EMR_GNR"}, + {name: "Homeless", msr: script.PrefetchersName, bit: 14, uarchs: "SPR_EMR_GNR"}, + {name: "LLC", msr: script.PrefetchersName, bit: 42, uarchs: "SPR_EMR_GNR"}, + } + var prefList []string + for _, pf := range prefetcherDefs { + if pf.uarchs == "all" || strings.Contains(pf.uarchs, uarch[:3]) { + msrVal := valFromRegexSubmatch(outputs[pf.msr].Stdout, `^([0-9a-fA-F]+)`) + if msrVal == "" { + continue + } + msrInt, err := strconv.ParseInt(msrVal, 16, 64) + if err != nil { + continue + } + bitMask := int64(math.Pow(2, float64(pf.bit))) + var enabledDisabled string + // enabled if bit is zero + if bitMask&msrInt == 0 { + enabledDisabled = "Enabled" + } else { + enabledDisabled = "Disabled" + } + prefList = append(prefList, fmt.Sprintf("%s: %s", pf.name, enabledDisabled)) + } + } + if len(prefList) > 0 { + return strings.Join(prefList, ", ") + } + return "None" +} + +// get L3 in MB from lscpu +// known lscpu output formats for L3 cache: +// +// 1.5 MBi < Ubuntu +// 1536KB < CentOS +func getL3LscpuMB(outputs map[string]script.ScriptOutput) (val float64, err error) { + l3Lscpu := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^L3 cache.*:\s*(.+?)$`) + re := regexp.MustCompile(`(\d+\.?\d*)\s*(\w+).*`) // match known formats + match := re.FindStringSubmatch(l3Lscpu) + if len(match) == 0 { + err = fmt.Errorf("unknown L3 format in lscpu: %s", l3Lscpu) + return + } + l3SizeNoUnit, err := strconv.ParseFloat(match[1], 64) + if err != nil { + err = fmt.Errorf("failed to parse L3 size from lscpu: %s, %v", l3Lscpu, err) + return + } + if strings.ToLower(match[2][:1]) == "m" { + val = l3SizeNoUnit + return + } + if strings.ToLower(match[2][:1]) == "k" { + val = l3SizeNoUnit / 1024 + return + } + err = fmt.Errorf("unknown L3 units in lscpu: %s", l3Lscpu) + return +} + +// GetL3LscpuMB exports getL3LscpuMB +func GetL3LscpuMB(outputs map[string]script.ScriptOutput) (val float64, err error) { + return getL3LscpuMB(outputs) +} + +func getCacheWays(uArch string) (cacheWays []int64) { + CPUdb := cpudb.NewCPUDB() + cpu, err := CPUdb.GetCPUByMicroArchitecture(uArch) + if err != nil { + return + } + return cpu.GetCacheWays() +} + +// GetCacheWays exports the getCacheWays function +func GetCacheWays(uArch string) (cacheWays []int64) { + return getCacheWays(uArch) +} + +// get L3 in MB from MSR +func getL3MSRMB(outputs map[string]script.ScriptOutput) (val float64, err error) { + uarch := uarchFromOutput(outputs) + if uarch == "" { + err = fmt.Errorf("uarch is required") + return + } + l3LscpuMB, err := getL3LscpuMB(outputs) + if err != nil { + return + } + l3MSRHex := strings.TrimSpace(outputs[script.L3WaySizeName].Stdout) + l3MSR, err := strconv.ParseInt(l3MSRHex, 16, 64) + if err != nil { + err = fmt.Errorf("failed to parse MSR output: %s", l3MSRHex) + return + } + cacheWays := getCacheWays(uarch) + if len(cacheWays) == 0 { + err = fmt.Errorf("failed to get cache ways for uArch: %s", uarch) + return + } + cpul3SizeGB := l3LscpuMB / 1024 + GBperWay := cpul3SizeGB / float64(len(cacheWays)) + for i, way := range cacheWays { + if way == l3MSR { + val = float64(i+1) * GBperWay * 1024 + return + } + } + err = fmt.Errorf("did not find %d in cache ways", l3MSR) + return +} + +// GetL3MSRMB exports the getL3MSRMB function +func GetL3MSRMB(outputs map[string]script.ScriptOutput) (val float64, err error) { + return getL3MSRMB(outputs) +} + +func l3FromOutput(outputs map[string]script.ScriptOutput) string { + l3, err := getL3MSRMB(outputs) + if err != nil { + slog.Info("Could not get L3 size from MSR, falling back to lscpu", slog.String("error", err.Error())) + l3, err = getL3LscpuMB(outputs) + if err != nil { + slog.Error("Could not get L3 size from lscpu", slog.String("error", err.Error())) + return "" + } + } + return fmt.Sprintf("%s MiB", strconv.FormatFloat(l3, 'f', -1, 64)) +} + +func l3PerCoreFromOutput(outputs map[string]script.ScriptOutput) string { + virtualization := valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Virtualization.*:\s*(.+?)$`) + if virtualization == "full" { + slog.Info("Can't calculate L3 per Core on virtualized host.") + return "" + } + l3, err := strconv.ParseFloat(strings.Split(l3FromOutput(outputs), " ")[0], 64) + if err != nil { + slog.Error("failed to parse L3 size", slog.String("error", err.Error())) + return "" + } + coresPerSocket, err := strconv.Atoi(valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket.*:\s*(.+?)$`)) + if err != nil || coresPerSocket == 0 { + slog.Error("failed to parse cores per socket", slog.String("error", err.Error())) + return "" + } + sockets, err := strconv.Atoi(valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(.*:\s*(.+?)$`)) + if err != nil || sockets == 0 { + slog.Error("failed to parse sockets", slog.String("error", err.Error())) + return "" + } + cacheMB := l3 / float64(coresPerSocket*sockets) + val := strconv.FormatFloat(cacheMB, 'f', 3, 64) + val = strings.TrimRight(val, "0") // trim trailing zeros + val = strings.TrimRight(val, ".") // trim decimal point if trailing + val += " MiB" + return val +} + +func acceleratorNames() []string { + var names []string + for _, accel := range accelDefs { + names = append(names, accel.Name) + } + return names +} + +func acceleratorCountsFromOutput(outputs map[string]script.ScriptOutput) []string { + var counts []string + lshw := outputs[script.LshwScriptName].Stdout + for _, accel := range accelDefs { + regex := fmt.Sprintf("%s:%s", accel.MfgID, accel.DevID) + re := regexp.MustCompile(regex) + count := len(re.FindAllString(lshw, -1)) + counts = append(counts, fmt.Sprintf("%d", count)) + } + return counts +} + +func acceleratorWorkQueuesFromOutput(outputs map[string]script.ScriptOutput) []string { + var queues []string + for _, accel := range accelDefs { + if accel.Name == "IAA" || accel.Name == "DSA" { + var scriptName string + if accel.Name == "IAA" { + scriptName = script.IaaDevicesScriptName + } else { + scriptName = script.DsaDevicesScriptName + } + devices := outputs[scriptName].Stdout + lines := strings.Split(devices, "\n") + // get non-empty lines + var nonEmptyLines []string + for _, line := range lines { + if strings.TrimSpace(line) != "" { + nonEmptyLines = append(nonEmptyLines, line) + } + } + if len(nonEmptyLines) == 0 { + queues = append(queues, "None") + } else { + queues = append(queues, strings.Join(nonEmptyLines, ", ")) + } + } else { + queues = append(queues, "N/A") + } + } + return queues +} + +func acceleratorFullNamesFromYaml() []string { + var fullNames []string + for _, accel := range accelDefs { + fullNames = append(fullNames, accel.FullName) + } + return fullNames +} + +func acceleratorDescriptionsFromYaml() []string { + var descriptions []string + for _, accel := range accelDefs { + descriptions = append(descriptions, accel.Description) + } + return descriptions +} + +func tdpFromOutput(outputs map[string]script.ScriptOutput) string { + msrHex := strings.TrimSpace(outputs[script.PackagePowerLimitName].Stdout) + msr, err := strconv.ParseInt(msrHex, 16, 0) + if err != nil || msr == 0 { + return "" + } + return fmt.Sprint(msr/8) + "W" +} + +func uncoreMinMaxFrequencyFromOutput(maxFreq bool, outputs map[string]script.ScriptOutput) string { + uarch := uarchFromOutput(outputs) + if uarch == "" { + slog.Error("failed to get uarch from script outputs") + return "" + } + var parsed int64 + var err error + if strings.Contains(uarch, "SRF") || strings.Contains(uarch, "GNR") { + re := regexp.MustCompile(`Read bits \d+:\d+ value (\d+) from TPMI ID .* for entry 0`) + found := false + var scriptName string + if maxFreq { + scriptName = script.UncoreMaxFromTPMIScriptName + } else { + scriptName = script.UncoreMinFromTPMIScriptName + } + for _, line := range strings.Split(outputs[scriptName].Stdout, "\n") { + match := re.FindStringSubmatch(line) + if len(match) > 0 { + found = true + parsed, err = strconv.ParseInt(match[1], 10, 64) + if err != nil { + slog.Error("failed to parse uncore frequency", slog.String("error", err.Error()), slog.String("line", line)) + return "" + } + break + } + } + if !found { + slog.Error("failed to find uncore frequency in TPMI output", slog.String("output", outputs[scriptName].Stdout)) + return "" + } + } else { + var scriptName string + if maxFreq { + scriptName = script.UncoreMaxFromMSRScriptName + } else { + scriptName = script.UncoreMinFromMSRScriptName + } + hex := strings.TrimSpace(outputs[scriptName].Stdout) + if hex != "" && hex != "0" { + parsed, err = strconv.ParseInt(hex, 16, 64) + if err != nil { + slog.Error("failed to parse uncore frequency", slog.String("error", err.Error()), slog.String("hex", hex)) + return "" + } + } else { + slog.Warn("failed to get uncore frequency from MSR", slog.String("hex", hex)) + return "" + } + } + return fmt.Sprintf("%.1fGHz", float64(parsed)/10) +} + +func uncoreMinFrequencyFromOutput(outputs map[string]script.ScriptOutput) string { + return uncoreMinMaxFrequencyFromOutput(false, outputs) +} + +func uncoreMaxFrequencyFromOutput(outputs map[string]script.ScriptOutput) string { + return uncoreMinMaxFrequencyFromOutput(true, outputs) +} + +func chaCountFromOutput(outputs map[string]script.ScriptOutput) string { + // output is the result of three rdmsr calls + // - client cha count + // - cha count + // - spr cha count + // stop when we find a non-zero value + // note: rdmsr writes to stderr on error so we will likely have fewer than 3 lines in stdout + for _, hexCount := range strings.Split(outputs[script.ChaCountScriptName].Stdout, "\n") { + if hexCount != "" && hexCount != "0" { + count, err := strconv.ParseInt(hexCount, 16, 64) + if err == nil { + return fmt.Sprintf("%d", count) + } + } + } + return "" +} + +func elcFieldValuesFromOutput(outputs map[string]script.ScriptOutput) (fieldValues []Field) { + if outputs[script.ElcScriptName].Stdout == "" { + return + } + r := csv.NewReader(strings.NewReader(outputs[script.ElcScriptName].Stdout)) + rows, err := r.ReadAll() + if err != nil { + return + } + if len(rows) < 2 { + return + } + // first row is headers + for fieldNamesIndex, fieldName := range rows[0] { + values := []string{} + // value rows + for _, row := range rows[1:] { + values = append(values, row[fieldNamesIndex]) + } + fieldValues = append(fieldValues, Field{Name: fieldName, Values: values}) + } + + // let's add an interpretation of the values in an additional column + values := []string{} + // value rows + for _, row := range rows[1:] { + var mode string + if row[2] == "IO" { + if row[5] == "0" && row[6] == "0" && row[7] == "0" { + mode = "Latency Optimized" + } else if row[5] == "800" && row[6] == "10" && row[7] == "94" { + mode = "Default" + } else { + mode = "Custom" + } + } else { // COMPUTE + if row[5] == "0" { + mode = "Latency Optimized" + } else if row[5] == "1200" { + mode = "Default" + } else { + mode = "Custom" + } + } + values = append(values, mode) + } + fieldValues = append(fieldValues, Field{Name: "Mode", Values: values}) + return +} + +func elcSummaryFromOutput(outputs map[string]script.ScriptOutput) string { + fieldValues := elcFieldValuesFromOutput(outputs) + if len(fieldValues) == 0 { + return "" + } + if len(fieldValues) < 10 { + return "" + } + if len(fieldValues[9].Values) == 0 { + return "" + } + summary := fieldValues[9].Values[0] + for _, value := range fieldValues[9].Values[1:] { + if value != summary { + return "mixed" + } + } + return summary +} + +/* + func turboBinsFromOutput(outputs map[string]script.ScriptOutput) string { + bins, err := getSpecCountFrequencies(outputs) + if err != nil { + return "" + } + var binStrings []string + beginInt := 1 + for _, bin := range bins { + count := bin[0] + endInt, _ := strconv.Atoi(count) + binStrings = append(binStrings, fmt.Sprintf("%d-%d: %sGHz", beginInt, endInt, bin[1])) + beginInt = endInt + 1 + } + return strings.Join(binStrings, ", ") + } +*/ +func epbFromOutput(outputs map[string]script.ScriptOutput) string { + var epb string + epbConsistent := true + for i, line := range strings.Split(outputs[script.EpbScriptName].Stdout, "\n") { + if line == "" { + continue + } + currentEpb := strings.TrimSpace(strings.Split(line, ":")[1]) + if i == 0 { + epb = currentEpb + continue + } + if currentEpb != epb { + epbConsistent = false + break + } + } + if !epbConsistent { + return "Varied" + } + msr, err := strconv.ParseInt(epb, 16, 0) + if err != nil { + return "" + } + var val string + if msr < 3 { + val = "Performance" + } else if msr < 6 { + val = "Balance Performance" + } else if msr == 6 { + val = "Normal" + } else if msr == 7 { + val = "Normal Powersave" + } else if msr == 8 { + val = "Balance Powersave" + } else { + val = "Powersave" + } + return fmt.Sprintf("%s (%d)", val, msr) +} + +func eppValToLabel(msr int) string { + var val string + if msr == 128 { + val = "Normal" + } else if msr < 128 && msr > 64 { + val = "Balance Performance" + } else if msr <= 64 { + val = "Performance" + } else if msr > 128 && msr < 192 { + val = "Balance Powersave" + } else { + val = "Powersave" + } + return fmt.Sprintf("%s (%d)", val, msr) +} + +func eppFromOutput(outputs map[string]script.ScriptOutput) string { + eppValidConsistent := true + var eppValid string + for i, line := range strings.Split(outputs[script.EppValidScriptName].Stdout, "\n") { + if line == "" { + continue + } + currentEpbValid := strings.TrimSpace(strings.Split(line, ":")[1]) + if i == 0 { + eppValid = currentEpbValid + continue + } + if currentEpbValid != eppValid { + eppValidConsistent = false + break + } + } + if eppValidConsistent && eppValid == "1" { + eppConsistent := true + var epp string + for i, line := range strings.Split(outputs[script.EppScriptName].Stdout, "\n") { + if line == "" { + continue + } + currentEpp := strings.TrimSpace(strings.Split(line, ":")[1]) + if i == 0 { + epp = currentEpp + continue + } + if currentEpp != epp { + eppConsistent = false + break + } + } + if eppConsistent { + msr, err := strconv.ParseInt(epp, 16, 0) + if err != nil { + return "epp parse error" + } + return eppValToLabel(int(msr)) + } else { + return "Varied" + } + } else if eppValidConsistent && eppValid == "0" { + eppPackage := strings.TrimSpace(outputs[script.EppPackageScriptName].Stdout) + msr, err := strconv.ParseInt(eppPackage, 16, 0) + if err != nil { + return "epp pkg parse error" + } + return eppValToLabel(int(msr)) + } else if eppValid != "" { + return "Varied" + } + return "" +} + +func operatingSystemFromOutput(outputs map[string]script.ScriptOutput) string { + os := valFromRegexSubmatch(outputs[script.EtcReleaseScriptName].Stdout, `^PRETTY_NAME=\"(.+?)\"`) + centos := valFromRegexSubmatch(outputs[script.EtcReleaseScriptName].Stdout, `^(CentOS Linux release .*)`) + if centos != "" { + os = centos + } + return os +} + +type cstateInfo struct { + Name string + Status string +} + +func cstatesFromOutput(outputs map[string]script.ScriptOutput) []cstateInfo { + var cstatesInfo []cstateInfo + output := outputs[script.CstatesScriptName].Stdout + for _, line := range strings.Split(output, "\n") { + if line == "" { + continue + } + parts := strings.Split(line, ",") + if len(parts) != 2 { + return nil + } + cstatesInfo = append(cstatesInfo, cstateInfo{Name: parts[0], Status: parts[1]}) + } + return cstatesInfo +} + +func cstatesSummaryFromOutput(outputs map[string]script.ScriptOutput) string { + cstatesInfo := cstatesFromOutput(outputs) + if cstatesInfo == nil { + return "" + } + summaryParts := []string{} + for _, cstateInfo := range cstatesInfo { + summaryParts = append(summaryParts, fmt.Sprintf("%s: %s", cstateInfo.Name, cstateInfo.Status)) + } + return strings.Join(summaryParts, ", ") +} + +type ISA struct { + Name string + FullName string + CPUID string +} + +var isas = []ISA{ + {"AES", "Advanced Encryption Standard New Instructions (AES-NI)", "AES instruction"}, + {"AMX", "Advanced Matrix Extensions (AMX)", "AMX-BF16: tile bfloat16 support"}, + {"AMX-COMPLEX", "AMX-COMPLEX Instruction", "AVX-COMPLEX instructions"}, + {"AMX-FP16", "AMX-FP16 Instruction", "AMX-FP16: FP16 tile operations"}, + {"AVX-IFMA", "AVX-IFMA Instruction", "AVX-IFMA: integer fused multiply add"}, + {"AVX-NE-CONVERT", "AVX-NE-CONVERT Instruction", "AVX-NE-CONVERT instructions"}, + {"AVX-VNNI-INT8", "AVX-VNNI-INT8 Instruction", "AVX-VNNI-INT8 instructions"}, + {"AVX512F", "AVX-512 Foundation", "AVX512F: AVX-512 foundation instructions"}, + {"AVX512_BF16", "Vector Neural Network Instructions (AVX512_BF16)", "AVX512_BF16: bfloat16 instructions"}, + {"AVX512_FP16", "Advanced Vector Extensions (AVX512_FP16)", "AVX512_FP16: fp16 support"}, + {"AVX512_VNNI", "Vector Neural Network Instructions (AVX512_VNNI)", "AVX512_VNNI: neural network instructions"}, + {"CLDEMOTE", "Cache Line Demote (CLDEMOTE)", "CLDEMOTE supports cache line demote"}, + {"CMPCCXADD", "Compare and Add if Condition is Met (CMPCCXADD)", "CMPccXADD instructions"}, + {"ENQCMD", "Enqueue Command Instruction (ENQCMD)", "ENQCMD instruction"}, + {"MOVDIRI", "Move Doubleword as Direct Store (MOVDIRI)", "MOVDIRI instruction"}, + {"MOVDIR64B", "Move 64 Bytes as Direct Store (MOVDIR64B)", "MOVDIR64B instruction"}, + {"PREFETCHIT0/1", "PREFETCHIT0/1 Instruction", "PREFETCHIT0, PREFETCHIT1 instructions"}, + {"SERIALIZE", "SERIALIZE Instruction", "SERIALIZE instruction"}, + {"SHA_NI", "SHA1/SHA256 Instruction Extensions (SHA_NI)", "SHA instructions"}, + {"TSXLDTRK", "Transactional Synchronization Extensions (TSXLDTRK)", "TSXLDTRK: TSX suspend load addr tracking"}, + {"VAES", "Vector AES", "VAES instructions"}, + {"WAITPKG", "UMONITOR, UMWAIT, TPAUSE Instructions", "WAITPKG instructions"}, +} + +func isaFullNames() []string { + var names []string + for _, isa := range isas { + names = append(names, isa.FullName) + } + return names +} + +func yesIfTrue(val string) string { + if val == "true" { + return "Yes" + } + return "No" +} + +func isaSupportedFromOutput(outputs map[string]script.ScriptOutput) []string { + var supported []string + for _, isa := range isas { + oneSupported := yesIfTrue(valFromRegexSubmatch(outputs[script.CpuidScriptName].Stdout, isa.CPUID+`\s*= (.+?)$`)) + supported = append(supported, oneSupported) + } + return supported +} + +func numaBalancingFromOutput(outputs map[string]script.ScriptOutput) string { + if strings.Contains(outputs[script.NumaBalancingScriptName].Stdout, "1") { + return "Enabled" + } else if strings.Contains(outputs[script.NumaBalancingScriptName].Stdout, "0") { + return "Disabled" + } + return "" +} + +type nicInfo struct { + Name string + Model string + Speed string + Link string + Bus string + Driver string + DriverVersion string + FirmwareVersion string + MACAddress string + NUMANode string + CPUAffinity string + IRQBalance string +} + +func nicInfoFromOutput(outputs map[string]script.ScriptOutput) []nicInfo { + // get nic names and models from lshw + namesAndModels := valsArrayFromRegexSubmatch(outputs[script.LshwScriptName].Stdout, `^\S+\s+(\S+)\s+network\s+([^\[]+?)(?:\s+\[.*\])?$`) + namesAndModels = append(namesAndModels, valsArrayFromRegexSubmatch(outputs[script.LshwScriptName].Stdout, `^usb.*? (\S+)\s+network\s+(\S.*?)$`)...) + if len(namesAndModels) == 0 { + return nil + } + + var nicInfos []nicInfo + for _, nameAndModel := range namesAndModels { + nicInfos = append(nicInfos, nicInfo{Name: nameAndModel[0], Model: nameAndModel[1]}) + } + // separate output from the nic info script into per-nic output + var perNicOutput [][]string + reBeginNicInfo := regexp.MustCompile(`Settings for (.*):`) + nicIndex := -1 + for _, line := range strings.Split(outputs[script.NicInfoScriptName].Stdout, "\n") { + match := reBeginNicInfo.FindStringSubmatch(line) + if len(match) > 0 { + nicIndex++ + perNicOutput = append(perNicOutput, []string{}) + } + if nicIndex == -1 { // we shouldn't be able to get here while nicIndex is -1 + slog.Warn("unexpected line in nic info output", slog.String("line", line)) + continue + } + perNicOutput[nicIndex] = append(perNicOutput[nicIndex], line) + } + if len(perNicOutput) != len(nicInfos) { + slog.Error("number of nics in lshw and nicinfo output do not match") + return nil + } + for nicIndex := range nicInfos { + for _, line := range perNicOutput[nicIndex] { + if strings.Contains(line, "Speed:") { + nicInfos[nicIndex].Speed = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.Contains(line, "Link detected:") { + nicInfos[nicIndex].Link = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.HasPrefix(line, "bus-info:") { + nicInfos[nicIndex].Bus = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.HasPrefix(line, "driver:") { + nicInfos[nicIndex].Driver = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.HasPrefix(line, "version:") { + nicInfos[nicIndex].DriverVersion = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.HasPrefix(line, "firmware-version:") { + nicInfos[nicIndex].FirmwareVersion = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.HasPrefix(line, "MAC Address:") { + nicInfos[nicIndex].MACAddress = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.HasPrefix(line, "NUMA Node:") { + nicInfos[nicIndex].NUMANode = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.Contains(line, "CPU Affinity:") { + nicInfos[nicIndex].CPUAffinity = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } else if strings.Contains(line, "IRQ Balance:") { + nicInfos[nicIndex].IRQBalance = strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } + } + } + return nicInfos +} + +type diskInfo struct { + Name string + Model string + Size string + MountPoint string + Type string + RequestQueueSize string + MinIOSize string + FirmwareVersion string + PCIeAddress string + NUMANode string + LinkSpeed string + LinkWidth string + MaxLinkSpeed string + MaxLinkWidth string +} + +func diskInfoFromOutput(outputs map[string]script.ScriptOutput) []diskInfo { + diskInfos := []diskInfo{} + for i, line := range strings.Split(outputs[script.DiskInfoScriptName].Stdout, "\n") { + // first line is the header + if i == 0 { + continue + } + if line == "" { + continue + } + fields := strings.Split(line, "|") + if len(fields) != 14 { + slog.Error("unexpected number of fields in disk info output", slog.String("line", line)) + return nil + } + // clean up the model name + fields[1] = strings.TrimSpace(fields[1]) + // if we don't have a firmware version, try to get it from another source + if fields[7] == "" { + reFwRev := regexp.MustCompile(`FwRev=(\w+)`) + reDev := regexp.MustCompile(fmt.Sprintf(`/dev/%s:`, fields[0])) + devFound := false + for _, line := range strings.Split(outputs[script.HdparmScriptName].Stdout, "\n") { + if !devFound { + if reDev.FindString(line) != "" { + devFound = true + continue + } + } else { + match := reFwRev.FindStringSubmatch(line) + if match != nil { + fields[7] = match[1] + break + } + } + } + } + diskInfos = append(diskInfos, diskInfo{fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7], fields[8], fields[9], fields[10], fields[11], fields[12], fields[13]}) + } + return diskInfos +} + +func filesystemFieldValuesFromOutput(outputs map[string]script.ScriptOutput) []Field { + fieldValues := []Field{} + reFindmnt := regexp.MustCompile(`(.*)\s(.*)\s(.*)\s(.*)`) + for i, line := range strings.Split(outputs[script.DfScriptName].Stdout, "\n") { + if line == "" { + continue + } + fields := strings.Fields(line) + // "Mounted On" gets split into two fields, rejoin + if i == 0 && fields[len(fields)-2] == "Mounted" && fields[len(fields)-1] == "on" { + fields[len(fields)-2] = "Mounted on" + fields = fields[:len(fields)-1] + for _, field := range fields { + fieldValues = append(fieldValues, Field{Name: field, Values: []string{}}) + } + // add an additional field + fieldValues = append(fieldValues, Field{Name: "Mount Options", Values: []string{}}) + continue + } + if len(fields) != len(fieldValues)-1 { + slog.Error("unexpected number of fields in df output", slog.String("line", line)) + return nil + } + for i, field := range fields { + fieldValues[i].Values = append(fieldValues[i].Values, field) + } + // get mount options for the current file system + var options string + for i, line := range strings.Split(outputs[script.FindMntScriptName].Stdout, "\n") { + if i == 0 { + continue + } + match := reFindmnt.FindStringSubmatch(line) + if match != nil { + target := match[1] + source := match[2] + if fields[0] == source && fields[5] == target { + options = match[4] + break + } + } + } + fieldValues[len(fieldValues)-1].Values = append(fieldValues[len(fieldValues)-1].Values, options) + } + return fieldValues +} + +type GPU struct { + Manufacturer string + Model string + PCIID string +} + +func gpuInfoFromOutput(outputs map[string]script.ScriptOutput) []GPU { + gpus := []GPU{} + gpusLshw := valsArrayFromRegexSubmatch(outputs[script.LshwScriptName].Stdout, `^pci.*?\s+display\s+(\w+).*?\s+\[(\w+):(\w+)]$`) + idxMfgName := 0 + idxMfgID := 1 + idxDevID := 2 + for _, gpu := range gpusLshw { + // Find GPU in GPU defs, note the model + var model string + for _, intelGPU := range IntelGPUs { + if gpu[idxMfgID] == intelGPU.MfgID { + model = intelGPU.Model + break + } + re := regexp.MustCompile(intelGPU.DevID) + if re.FindString(gpu[idxDevID]) != "" { + model = intelGPU.Model + break + } + } + if model == "" { + if gpu[idxMfgID] == "8086" { + model = "Unknown Intel" + } else { + model = "Unknown" + } + } + gpus = append(gpus, GPU{Manufacturer: gpu[idxMfgName], Model: model, PCIID: gpu[idxMfgID] + ":" + gpu[idxDevID]}) + } + return gpus +} + +type Gaudi struct { + ModuleID string + SerialNumber string + BusID string + DriverVersion string + EROM string + CPLD string + SPI string + NUMA string +} + +// output from the GaudiInfo script: +// module_id, serial, bus_id, driver_version +// 2, AM50016189, 0000:19:00.0, 1.17.0-28a11ca +// 6, AM50016165, 0000:b3:00.0, 1.17.0-28a11ca +// 3, AM50016119, 0000:1a:00.0, 1.17.0-28a11ca +// 0, AM50016134, 0000:43:00.0, 1.17.0-28a11ca +// 7, AM50016150, 0000:b4:00.0, 1.17.0-28a11ca +// 1, AM50016130, 0000:44:00.0, 1.17.0-28a11ca +// 4, AM50016127, 0000:cc:00.0, 1.17.0-28a11ca +// 5, AM50016122, 0000:cd:00.0, 1.17.0-28a11ca +// +// output from the GaudiNuma script: +// modID NUMA Affinity +// ----- ------------- +// 0 0 +// 1 0 +// 2 0 +// 3 0 +// 4 1 +// 5 1 +// 6 1 +// 7 1 +// +// output from the GaudiFirmware script: +// [0] AIP (accel0) 0000:19:00.0 +// erom +// component : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 (Jul 24 2024 - 11:31:46) +// fw os : +// fuse +// component : 01P0-HL2080A0-15-TF8A81-03-07-03 +// fw os : +// cpld +// component : 0x00000010.653FB250 +// fw os : +// uboot +// component : U-Boot 2021.04-hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// fw os : +// arcmp +// component : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// fw os : Linux gaudi2 5.10.18-hl-gaudi2-1.17.0-fw-51.2.0-sec-9 #1 SMP PREEMPT Wed Jul 24 11:44:52 IDT 2024 aarch64 GNU/Linux +// +// preboot +// component : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// fw os : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// eeprom : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// boot_info : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// mgmt +// component : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// fw os : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// i2c : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// eeprom : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// boot_info : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// pid +// component : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// fw os : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// eeprom : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// boot_info : hl-gaudi2-1.17.0-fw-51.2.0-sec-9 +// +// [1] AIP (accel1) 0000:b3:00.0 ....... + +func gaudiInfoFromOutput(outputs map[string]script.ScriptOutput) []Gaudi { + gaudis := []Gaudi{} + for i, line := range strings.Split(outputs[script.GaudiInfoScriptName].Stdout, "\n") { + if line == "" || i == 0 { // skip blank lines and header + continue + } + fields := strings.Split(line, ", ") + if len(fields) != 4 { + slog.Error("unexpected number of fields in gaudi info output", slog.String("line", line)) + continue + } + gaudis = append(gaudis, Gaudi{ModuleID: fields[0], SerialNumber: fields[1], BusID: fields[2], DriverVersion: fields[3]}) + } + // sort the gaudis by module ID + sort.Slice(gaudis, func(i, j int) bool { + return gaudis[i].ModuleID < gaudis[j].ModuleID + }) + // get NUMA affinity + numaAffinities := valsArrayFromRegexSubmatch(outputs[script.GaudiNumaScriptName].Stdout, `^(\d+)\s+(\d+)\s+$`) + if len(numaAffinities) != len(gaudis) { + slog.Error("number of gaudis in gaudi info and numa output do not match", slog.Int("gaudis", len(gaudis)), slog.Int("numaAffinities", len(numaAffinities))) + return nil + } + for i, numaAffinity := range numaAffinities { + gaudis[i].NUMA = numaAffinity[1] + } + // get firmware versions + reDevice := regexp.MustCompile(`^\[(\d+)] AIP \(accel\d+\) (.*)$`) + reErom := regexp.MustCompile(`\s+erom$`) + reCpld := regexp.MustCompile(`\s+cpld$`) + rePreboot := regexp.MustCompile(`\s+preboot$`) + reComponent := regexp.MustCompile(`^\s+component\s+:\s+hl-gaudi\d-(.*)-sec-\d+`) + reCpldComponent := regexp.MustCompile(`^\s+component\s+:\s+(0x[0-9a-fA-F]+\.[0-9a-fA-F]+)$`) + deviceIdx := -1 + state := -1 + for _, line := range strings.Split(outputs[script.GaudiFirmwareScriptName].Stdout, "\n") { + if line == "" { + continue + } + match := reDevice.FindStringSubmatch(line) + if match != nil { + var err error + deviceIdx, err = strconv.Atoi(match[1]) + if err != nil { + slog.Error("failed to parse device index", slog.String("deviceIdx", match[1])) + return nil + } + if deviceIdx >= len(gaudis) { + slog.Error("device index out of range", slog.Int("deviceIdx", deviceIdx), slog.Int("gaudis", len(gaudis))) + return nil + } + continue + } + if deviceIdx == -1 { + continue + } + if reErom.FindString(line) != "" { + state = 0 + continue + } + if reCpld.FindString(line) != "" { + state = 1 + continue + } + if rePreboot.FindString(line) != "" { + state = 2 + continue + } + if state != -1 { + switch state { + case 0: + match := reComponent.FindStringSubmatch(line) + if match != nil { + gaudis[deviceIdx].EROM = match[1] + } + case 1: + match := reCpldComponent.FindStringSubmatch(line) + if match != nil { + gaudis[deviceIdx].CPLD = match[1] + } + case 2: + match := reComponent.FindStringSubmatch(line) + if match != nil { + gaudis[deviceIdx].SPI = match[1] + } + } + state = -1 + } + } + return gaudis +} + +// return all PCI Devices of specified class +func getPCIDevices(class string, outputs map[string]script.ScriptOutput) (devices []map[string]string) { + device := make(map[string]string) + re := regexp.MustCompile(`^(\w+):\s+(.*)$`) + for _, line := range strings.Split(outputs[script.LspciVmmScriptName].Stdout, "\n") { + if line == "" { // end of device + if devClass, ok := device["Class"]; ok { + if devClass == class { + devices = append(devices, device) + } + } + device = make(map[string]string) + continue + } + match := re.FindStringSubmatch(line) + if len(match) > 0 { + key := match[1] + value := match[2] + device[key] = value + } + } + return +} + +func cveInfoFromOutput(outputs map[string]script.ScriptOutput) [][]string { + vulns := make(map[string]string) + // from spectre-meltdown-checker + for _, pair := range valsArrayFromRegexSubmatch(outputs[script.CveScriptName].Stdout, `(CVE-\d+-\d+): (.+)`) { + vulns[pair[0]] = pair[1] + } + // sort the vulnerabilities by CVE ID + var ids []string + for id := range vulns { + ids = append(ids, id) + } + sort.Strings(ids) + cves := make([][]string, 0) + for _, id := range ids { + cves = append(cves, []string{id, vulns[id]}) + } + return cves +} + +/* "1,3-5,8" -> [1,3,4,5,8] */ +func expandCPUList(cpuList string) (cpus []int) { + if cpuList != "" { + tokens := strings.Split(cpuList, ",") + for _, token := range tokens { + if strings.Contains(token, "-") { + subTokens := strings.Split(token, "-") + if len(subTokens) == 2 { + begin, errA := strconv.Atoi(subTokens[0]) + end, errB := strconv.Atoi(subTokens[1]) + if errA != nil || errB != nil { + slog.Warn("Failed to parse CPU affinity", slog.String("cpuList", cpuList)) + return + } + for i := begin; i <= end; i++ { + cpus = append(cpus, i) + } + } + } else { + cpu, err := strconv.Atoi(token) + if err != nil { + slog.Warn("CPU isn't an integer!", slog.String("cpuList", cpuList)) + return + } + cpus = append(cpus, cpu) + } + } + } + return +} + +func nicIRQMappingsFromOutput(outputs map[string]script.ScriptOutput) [][]string { + nicInfos := nicInfoFromOutput(outputs) + if len(nicInfos) == 0 { + return nil + } + nicIRQMappings := [][]string{} + for _, nic := range nicInfos { + // command output is formatted like this: 200:1;201:1-17,36-53;202:44 + // which is : + // we need to reverse it to : + cpuIRQMappings := make(map[int][]int) + irqCPUPairs := strings.Split(nic.CPUAffinity, ";") + for _, pair := range irqCPUPairs { + if pair == "" { + continue + } + tokens := strings.Split(pair, ":") + irq, err := strconv.Atoi(tokens[0]) + if err != nil { + continue + } + cpuList := tokens[1] + cpus := expandCPUList(cpuList) + for _, cpu := range cpus { + cpuIRQMappings[cpu] = append(cpuIRQMappings[cpu], irq) + } + } + var cpuIRQs []string + var cpus []int + for k := range cpuIRQMappings { + cpus = append(cpus, k) + } + sort.Ints(cpus) + for _, cpu := range cpus { + irqs := cpuIRQMappings[cpu] + cpuIRQ := fmt.Sprintf("%d:", cpu) + var irqStrings []string + for _, irq := range irqs { + irqStrings = append(irqStrings, fmt.Sprintf("%d", irq)) + } + cpuIRQ += strings.Join(irqStrings, ",") + cpuIRQs = append(cpuIRQs, cpuIRQ) + } + nicIRQMappings = append(nicIRQMappings, []string{nic.Name, strings.Join(cpuIRQs, " ")}) + } + return nicIRQMappings +} + +func nicSummaryFromOutput(outputs map[string]script.ScriptOutput) string { + nics := nicInfoFromOutput(outputs) + if len(nics) == 0 { + return "N/A" + } + modelCount := make(map[string]int) + for _, nic := range nics { + modelCount[nic.Model]++ + } + var summary []string + for model, count := range modelCount { + summary = append(summary, fmt.Sprintf("%dx %s", count, model)) + } + return strings.Join(summary, ", ") +} + +func diskSummaryFromOutput(outputs map[string]script.ScriptOutput) string { + disks := diskInfoFromOutput(outputs) + if len(disks) == 0 { + return "N/A" + } + type ModelSize struct { + model string + size string + } + modelSizeCount := make(map[ModelSize]int) + for _, disk := range disks { + if disk.Model == "" { + continue + } + modelSize := ModelSize{model: disk.Model, size: disk.Size} + modelSizeCount[modelSize]++ + } + var summary []string + for modelSize, count := range modelSizeCount { + summary = append(summary, fmt.Sprintf("%dx %s %s", count, modelSize.size, modelSize.model)) + } + return strings.Join(summary, ", ") +} + +func acceleratorSummaryFromOutput(outputs map[string]script.ScriptOutput) string { + var summary []string + accelerators := acceleratorNames() + counts := acceleratorCountsFromOutput(outputs) + for i, name := range accelerators { + if strings.Contains(name, "chipset") { // skip "QAT (on chipset)" in this table + continue + } else if strings.Contains(name, "CPU") { // rename "QAT (on CPU) to simply "QAT" + name = "QAT" + } + summary = append(summary, fmt.Sprintf("%s %s [0]", name, counts[i])) + } + return strings.Join(summary, ", ") +} + +func cveSummaryFromOutput(outputs map[string]script.ScriptOutput) string { + cves := cveInfoFromOutput(outputs) + if len(cves) == 0 { + return "" + } + var numOK int + var numVuln int + for _, cve := range cves { + if strings.HasPrefix(cve[1], "OK") { + numOK++ + } else { + numVuln++ + } + } + return fmt.Sprintf("%d OK, %d Vulnerable", numOK, numVuln) +} + +func systemSummaryFromOutput(outputs map[string]script.ScriptOutput) string { + // BASELINE: 1-node, 2x IntelĀ® XeonĀ® , xx cores, 100W TDP, HT On/Off?, Turbo On/Off?, Total Memory xxx GB (xx slots/ xx GB/ xxxx MHz [run @ xxxx MHz] ), , , , . Test by Intel as of . + template := "1-node, %sx %s, %s cores, %s TDP, HT %s, Turbo %s, Total Memory %s, BIOS %s, microcode %s, %s, %s, %s, %s. Test by Intel as of %s." + var socketCount, cpuModel, coreCount, tdp, htOnOff, turboOnOff, installedMem, biosVersion, uCodeVersion, nics, disks, operatingSystem, kernelVersion, date string + + // socket count + socketCount = valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Socket\(s\):\s*(\d+)$`) + // CPU model + cpuModel = valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Model name:\s*(.+?)$`) + // core count + coreCount = valFromRegexSubmatch(outputs[script.LscpuScriptName].Stdout, `^Core\(s\) per socket:\s*(\d+)$`) + // TDP + tdp = tdpFromOutput(outputs) + if tdp == "" { + tdp = "?" + } + // hyperthreading + htOnOff = hyperthreadingFromOutput(outputs) + if htOnOff == "Enabled" { + htOnOff = "On" + } else if htOnOff == "Disabled" { + htOnOff = "Off" + } else if htOnOff == "N/A" { + htOnOff = "N/A" + } else { + htOnOff = "?" + } + // turbo + turboOnOff = turboEnabledFromOutput(outputs) + if strings.Contains(strings.ToLower(turboOnOff), "enabled") { + turboOnOff = "On" + } else if strings.Contains(strings.ToLower(turboOnOff), "disabled") { + turboOnOff = "Off" + } else { + turboOnOff = "?" + } + // memory + installedMem = installedMemoryFromOutput(outputs) + // BIOS + biosVersion = valFromRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, `^Version:\s*(.+?)$`) + // microcode + uCodeVersion = valFromRegexSubmatch(outputs[script.ProcCpuinfoScriptName].Stdout, `^microcode.*:\s*(.+?)$`) + // NICs + nics = nicSummaryFromOutput(outputs) + // disks + disks = diskSummaryFromOutput(outputs) + // OS + operatingSystem = operatingSystemFromOutput(outputs) + // kernel + kernelVersion = valFromRegexSubmatch(outputs[script.UnameScriptName].Stdout, `^Linux \S+ (\S+)`) + // date + date = strings.TrimSpace(outputs[script.DateScriptName].Stdout) + // put it all together + return fmt.Sprintf(template, socketCount, cpuModel, coreCount, tdp, htOnOff, turboOnOff, installedMem, biosVersion, uCodeVersion, nics, disks, operatingSystem, kernelVersion, date) +} + +func getSectionsFromOutput(outputs map[string]script.ScriptOutput, scriptName string) map[string]string { + reHeader := regexp.MustCompile(`^##########\s+(.+)\s+##########$`) + sections := make(map[string]string, 0) + var header string + var sectionLines []string + lines := strings.Split(outputs[scriptName].Stdout, "\n") + lineCount := len(lines) + if lineCount == 1 && lines[0] == "" { + return sections + } + for idx, line := range lines { + match := reHeader.FindStringSubmatch(line) + if match != nil { + if header != "" { + sections[header] = strings.Join(sectionLines, "\n") + sectionLines = []string{} + } + header = match[1] + if _, ok := sections[header]; ok { + log.Panic("can't have same header twice") + } + continue + } + sectionLines = append(sectionLines, line) + if idx == lineCount-1 { + sections[header] = strings.Join(sectionLines, "\n") + } + } + return sections +} + +func javaFoldedFromOutput(outputs map[string]script.ScriptOutput) string { + sections := getSectionsFromOutput(outputs, script.ProfileJavaScriptName) + javaFolded := make(map[string]string) + re := regexp.MustCompile(`^async-profiler (\d+) (.*)$`) + for header, stacks := range sections { + if stacks == "" { + slog.Info("no stacks for java process", slog.String("header", header)) + continue + } + if strings.HasPrefix(stacks, "Failed to inject profiler") { + slog.Warn("profiling data error", slog.String("header", header)) + continue + } + match := re.FindStringSubmatch(header) + if match == nil { + slog.Warn("profiling data error, regex didn't match header", slog.String("header", header)) + continue + } + pid := match[1] + processName := match[2] + _, ok := javaFolded[processName] + if processName == "" { + processName = "java (" + pid + ")" + } else if ok { + processName = processName + " (" + pid + ")" + } + javaFolded[processName] = stacks + } + folded, err := mergeJavaFolded(javaFolded) + if err != nil { + slog.Warn("err merging java stacks", slog.String("error", err.Error())) + } + return folded +} + +func systemFoldedFromOutput(outputs map[string]script.ScriptOutput) string { + sections := getSectionsFromOutput(outputs, script.ProfileSystemScriptName) + var dwarfFolded, fpFolded string + for header, content := range sections { + if header == "perf_dwarf" { + dwarfFolded = content + } else if header == "perf_fp" { + fpFolded = content + } + } + if dwarfFolded == "" && fpFolded == "" { + return "" + } + folded, err := mergeSystemFolded(fpFolded, dwarfFolded) + if err != nil { + slog.Warn("error merging folded stacks", slog.String("error", err.Error())) + } + return folded +} diff --git a/internal/script/script.go b/internal/script/script.go new file mode 100644 index 0000000..25d8acf --- /dev/null +++ b/internal/script/script.go @@ -0,0 +1,470 @@ +// Package script provides functions to run scripts on a target and get the output. +package script + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "embed" + "fmt" + "log/slog" + "os" + "os/exec" + "path" + "strconv" + "strings" + + "perfspect/internal/target" + "perfspect/internal/util" +) + +//go:embed resources +var Resources embed.FS + +type ScriptDefinition struct { + Name string // just a name + Script string // the bash script that will be run + Architectures []string // architectures, i.e., x86_64, arm64. If empty, it will run on all architectures. + Families []string // families, e.g., 6, 7. If empty, it will run on all families. + Models []string // models, e.g., 62, 63. If empty, it will run on all models. + Lkms []string // loadable kernel modules + Depends []string // binary dependencies that must be available for the script to run + Superuser bool // requires sudo or root + Sequential bool // run script sequentially (not at the same time as others) + Timeout int // seconds +} + +type ScriptOutput struct { + ScriptDefinition + Stdout string + Stderr string + Exitcode int +} + +// RunScript runs a script on the specified target and returns the output. +func RunScript(myTarget target.Target, script ScriptDefinition, localTempDir string) (scriptOutput ScriptOutput, err error) { + targetArchitecture, err := myTarget.GetArchitecture() + if err != nil { + err = fmt.Errorf("error getting target architecture: %v", err) + return + } + targetFamily, err := myTarget.GetFamily() + if err != nil { + err = fmt.Errorf("error getting target family: %v", err) + return + } + targetModel, err := myTarget.GetModel() + if err != nil { + err = fmt.Errorf("error getting target model: %v", err) + return + } + if len(script.Architectures) > 0 && !util.StringInList(targetArchitecture, script.Architectures) || + len(script.Families) > 0 && !util.StringInList(targetFamily, script.Families) || + len(script.Models) > 0 && !util.StringInList(targetModel, script.Models) { + err = fmt.Errorf("\"%s\" script is not intended for the target processor", script.Name) + return + } + scriptOutputs, err := RunScripts(myTarget, []ScriptDefinition{script}, false, localTempDir) + scriptOutput = scriptOutputs[script.Name] + return +} + +// RunScripts runs a list of scripts on a target and returns the outputs of each script as a map with the script name as the key. +func RunScripts(myTarget target.Target, scripts []ScriptDefinition, ignoreScriptErrors bool, localTempDir string) (map[string]ScriptOutput, error) { + // need a unique temp directory for each target to avoid race conditions + localTempDirForTarget := path.Join(localTempDir, myTarget.GetName()) + // if the directory doesn't exist, create it + if _, err := os.Stat(localTempDirForTarget); os.IsNotExist(err) { + if err := os.Mkdir(localTempDirForTarget, 0755); err != nil { + err = fmt.Errorf("error creating directory for target: %v", err) + return nil, err + } + } + targetArchitecture, err := myTarget.GetArchitecture() + if err != nil { + err = fmt.Errorf("error getting target architecture: %v", err) + return nil, err + } + targetFamily, err := myTarget.GetFamily() + if err != nil { + err = fmt.Errorf("error getting target family: %v", err) + return nil, err + } + targetModel, err := myTarget.GetModel() + if err != nil { + err = fmt.Errorf("error getting target model: %v", err) + return nil, err + } + // drop scripts that should not be run and separate scripts that must run sequentially from those that can be run in parallel + canElevate := myTarget.CanElevatePrivileges() + var sequentialScripts []ScriptDefinition + var parallelScripts []ScriptDefinition + for _, script := range scripts { + if len(script.Architectures) > 0 && !util.StringInList(targetArchitecture, script.Architectures) || + len(script.Families) > 0 && !util.StringInList(targetFamily, script.Families) || + len(script.Models) > 0 && !util.StringInList(targetModel, script.Models) { + slog.Info("skipping script because it is not intended to run on the target processor", slog.String("target", myTarget.GetName()), slog.String("script", script.Name), slog.String("targetArchitecture", targetArchitecture), slog.String("targetFamily", targetFamily), slog.String("targetModel", targetModel)) + continue + } + if script.Superuser && !canElevate { + slog.Info("skipping script because it requires superuser privileges and the target cannot elevate privileges", slog.String("script", script.Name)) + continue + } + if script.Sequential { + sequentialScripts = append(sequentialScripts, script) + } else { + parallelScripts = append(parallelScripts, script) + } + } + + // prepare target to run scripts by copying scripts and dependencies to target and installing LKMs + installedLkms, err := prepareTargetToRunScripts(myTarget, append(sequentialScripts, parallelScripts...), localTempDirForTarget, false) + if err != nil { + err = fmt.Errorf("error while preparing target to run scripts: %v", err) + return nil, err + } + if len(installedLkms) > 0 { + defer myTarget.UninstallLkms(installedLkms) + } + + // if there's only 1 parallel script, run it sequentially + if len(parallelScripts) == 1 { + slog.Debug("running single parallel script sequentially", slog.String("script", parallelScripts[0].Name)) + sequentialScripts = append(sequentialScripts, parallelScripts...) + parallelScripts = nil + } + scriptOutputs := make(map[string]ScriptOutput) + // run parallel scripts + if len(parallelScripts) > 0 { + // form one master script that calls all the parallel scripts in the background + masterScriptName := "parallel_master.sh" + masterScript, needsElevatedPrivileges := formMasterScript(myTarget, parallelScripts) + // write master script to local file + masterScriptPath := path.Join(localTempDirForTarget, masterScriptName) + err = os.WriteFile(masterScriptPath, []byte(masterScript), 0644) + if err != nil { + err = fmt.Errorf("error writing master script to local file: %v", err) + return nil, err + } + // copy master script to target + err = myTarget.PushFile(masterScriptPath, myTarget.GetTempDirectory()) + if err != nil { + err = fmt.Errorf("error copying script to target: %v", err) + return nil, err + } + // run master script on target + // if the master script requires elevated privileges, we run it with sudo + // Note: adding 'sudo' to the individual scripts inside the master script + // instigates a known bug in the terminal that corrupts the tty settings: + // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1043320 + var cmd *exec.Cmd + if needsElevatedPrivileges { + // run master script with sudo, "-S" to read password from stdin + cmd = exec.Command("sudo", "-S", "bash", path.Join(myTarget.GetTempDirectory(), masterScriptName)) + } else { + cmd = exec.Command("bash", path.Join(myTarget.GetTempDirectory(), masterScriptName)) + } + stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0) + if err != nil { + slog.Error("error running master script on target", slog.String("stdout", stdout), slog.String("stderr", stderr), slog.Int("exitcode", exitcode), slog.String("error", err.Error())) + return nil, err + } + // parse output of master script + parallelScriptOutputs := parseMasterScriptOutput(stdout) + for _, scriptOutput := range parallelScriptOutputs { + // find associated parallel script + scriptIdx := -1 + for i, script := range parallelScripts { + if script.Name == scriptOutput.Name { + scriptIdx = i + break + } + } + scriptOutput.Script = parallelScripts[scriptIdx].Script + scriptOutputs[scriptOutput.Name] = scriptOutput + } + } + // run sequential scripts + for _, script := range sequentialScripts { + cmd := prepareCommand(script, myTarget.GetTempDirectory()) + stdout, stderr, exitcode, err := myTarget.RunCommand(cmd, 0) + if err != nil { + slog.Error("error running script on target", slog.String("script", script.Script), slog.String("stdout", stdout), slog.String("stderr", stderr), slog.Int("exitcode", exitcode), slog.String("error", err.Error())) + } + scriptOutputs[script.Name] = ScriptOutput{ScriptDefinition: script, Stdout: stdout, Stderr: stderr, Exitcode: exitcode} + if !ignoreScriptErrors { + return scriptOutputs, err + } + err = nil + } + return scriptOutputs, nil +} + +// RunScriptAsync runs a script on the specified target and returns the output. It is meant to be called +// in a go routine. +func RunScriptAsync(myTarget target.Target, script ScriptDefinition, localTempDir string, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, errorChannel chan error, cmdChannel chan *exec.Cmd) { + // need a unique temp directory for each target to avoid race conditions when there are multiple targets + localTempDirForTarget := path.Join(localTempDir, myTarget.GetName()) + // if the directory doesn't exist, create it + // we try a few times because there could multiple go routines trying to create the same directory + maxTries := 3 + for i := 0; i < maxTries; i++ { + if _, err := os.Stat(localTempDirForTarget); os.IsNotExist(err) { + if err := os.Mkdir(localTempDirForTarget, 0755); err != nil { + if i == maxTries-1 { + err = fmt.Errorf("error creating local temp directory for target: %v", err) + errorChannel <- err + return + } + } + } + } + targetArchitecture, err := myTarget.GetArchitecture() + if err != nil { + err = fmt.Errorf("error getting target architecture: %v", err) + errorChannel <- err + return + } + if len(script.Architectures) > 0 && !util.StringInList(targetArchitecture, script.Architectures) { + err = fmt.Errorf("skipping script because it is not meant for this architecture: %s", targetArchitecture) + errorChannel <- err + return + } + installedLkms, err := prepareTargetToRunScripts(myTarget, []ScriptDefinition{script}, localTempDirForTarget, true) + if err != nil { + err = fmt.Errorf("error while preparing target to run script: %v", err) + errorChannel <- err + return + } + if len(installedLkms) != 0 { + defer myTarget.UninstallLkms(installedLkms) + } + cmd := prepareCommand(script, myTarget.GetTempDirectory()) + err = myTarget.RunCommandAsync(cmd, stdoutChannel, stderrChannel, exitcodeChannel, script.Timeout, cmdChannel) + errorChannel <- err +} + +func prepareCommand(script ScriptDefinition, targetTempDirectory string) (cmd *exec.Cmd) { + scriptPath := path.Join(targetTempDirectory, scriptNameToFilename(script.Name)) + if script.Superuser { + cmd = exec.Command("sudo", "bash", scriptPath) + } else { + cmd = exec.Command("bash", scriptPath) + } + return +} + +func sanitizeScriptName(name string) string { + sanitized := strings.ReplaceAll(name, " ", "_") + sanitized = strings.ReplaceAll(sanitized, "-", "_") + return sanitized +} + +func scriptNameToFilename(name string) string { + return sanitizeScriptName(name) + ".sh" +} + +// formMasterScript forms a master script that runs all parallel scripts in the background, waits for them to finish, then prints the output of each script. +// Return values are the master script and a boolean indicating whether the master script requires elevated privileges. +func formMasterScript(myTarget target.Target, parallelScripts []ScriptDefinition) (string, bool) { + // we write the stdout and stderr from each command to temporary files and save the PID of each command + // in a variable named after the script + var masterScript strings.Builder + targetTempDirectory := myTarget.GetTempDirectory() + masterScript.WriteString(fmt.Sprintf("script_dir=%s\n", targetTempDirectory)) + // change working directory to target temporary directory in case any of the scripts write out temporary files + masterScript.WriteString(fmt.Sprintf("cd %s\n", targetTempDirectory)) + // the master script will run all parallel scripts in the background + masterScript.WriteString("\n# run all scripts in the background\n") + needsElevatedPrivileges := false + for _, script := range parallelScripts { + if script.Superuser { + needsElevatedPrivileges = true + } + masterScript.WriteString( + fmt.Sprintf("bash %s > %s 2>%s &\n", + path.Join("$script_dir", scriptNameToFilename(script.Name)), + path.Join("$script_dir", sanitizeScriptName(script.Name)+".stdout"), + path.Join("$script_dir", sanitizeScriptName(script.Name)+".stderr"), + ), + ) + masterScript.WriteString(fmt.Sprintf("%s_pid=$!\n", sanitizeScriptName(script.Name))) + } + // the master script will wait for all parallel scripts to finish + masterScript.WriteString("\n# wait for all scripts to finish\n") + for _, script := range parallelScripts { + masterScript.WriteString(fmt.Sprintf("wait \"$%s_pid\"\n", sanitizeScriptName(script.Name))) + masterScript.WriteString(fmt.Sprintf("%s_exitcode=$?\n", sanitizeScriptName(script.Name))) + } + // the master script will print the output of each script + masterScript.WriteString("\n# print output of each script\n") + for _, script := range parallelScripts { + masterScript.WriteString("echo \"<---------------------->\"\n") + masterScript.WriteString(fmt.Sprintf("echo SCRIPT NAME: %s\n", script.Name)) + masterScript.WriteString(fmt.Sprintf("echo STDOUT:\ncat %s\n", path.Join("$script_dir", sanitizeScriptName(script.Name)+".stdout"))) + masterScript.WriteString(fmt.Sprintf("echo STDERR:\ncat %s\n", path.Join("$script_dir", sanitizeScriptName(script.Name)+".stderr"))) + masterScript.WriteString(fmt.Sprintf("echo EXIT CODE: $%s_exitcode\n", sanitizeScriptName(script.Name))) + } + return masterScript.String(), needsElevatedPrivileges +} + +// parseMasterScriptOutput parses the output of the master script that runs all parallel scripts in the background. +// It returns a list of ScriptOutput objects, one for each script that was run. +func parseMasterScriptOutput(masterScriptOutput string) (scriptOutputs []ScriptOutput) { + // split output of master script into individual script outputs + outputs := strings.Split(masterScriptOutput, "<---------------------->\n") + for _, output := range outputs { + lines := strings.Split(output, "\n") + if len(lines) < 4 { // minimum lines for a script output + continue + } + if !strings.HasPrefix(lines[0], "SCRIPT NAME: ") { + slog.Warn("skipping output because it does not contain script name", slog.String("output", output)) + continue + } + scriptName := strings.TrimSpace(strings.TrimPrefix(lines[0], "SCRIPT NAME: ")) + var stdout string + var stderr string + var exitcode string + var stdoutLines []string + var stderrLines []string + stdoutStarted := false + stderrStarted := false + for _, line := range lines[1:] { + if strings.HasPrefix(line, "STDOUT:") { + stdoutStarted = true + stderrStarted = false + continue + } + if strings.HasPrefix(line, "STDERR:") { + stderrStarted = true + stdoutStarted = false + continue + } + if strings.HasPrefix(line, "EXIT CODE:") { + exitcode = strings.TrimSpace(strings.TrimPrefix(line, "EXIT CODE:")) + stdoutStarted = false + stderrStarted = false + break + } + if stdoutStarted { + stdoutLines = append(stdoutLines, line) + } else if stderrStarted { + stderrLines = append(stderrLines, line) + } + } + if len(stdoutLines) > 0 { + stdoutLines = append(stdoutLines, "") // add a newline at the end to match the original output + } + if len(stderrLines) > 0 { + stderrLines = append(stderrLines, "") // add a newline at the end to match the original output + } + stdout = strings.Join(stdoutLines, "\n") + stderr = strings.Join(stderrLines, "\n") + exitCodeInt, err := strconv.Atoi(exitcode) + if err != nil { + slog.Error("error converting exit code to integer, setting to -100", slog.String("exitcode", exitcode), slog.String("error", err.Error())) + exitCodeInt = -100 + } + scriptOutputs = append(scriptOutputs, ScriptOutput{ + ScriptDefinition: ScriptDefinition{Name: scriptName}, + Stdout: stdout, + Stderr: stderr, + Exitcode: exitCodeInt, + }) + } + return +} + +// prepareTargetToRunScripts prepares the target to run the specified scripts by copying the scripts and their dependencies to the target and installing the required LKMs on the target. +func prepareTargetToRunScripts(myTarget target.Target, scripts []ScriptDefinition, localTempDir string, failIfDependencyNotFound bool) (installedLkms []string, err error) { + // verify temporary directory exists on target + targetTempDirectory := myTarget.GetTempDirectory() + if targetTempDirectory == "" { + panic("target temporary directory cannot be empty") + } + lkmsToInstall := make(map[string]int) + dependenciesToCopy := make(map[string]int) + userPath, err := myTarget.GetUserPath() + if err != nil { + err = fmt.Errorf("error while retrieving user's path: %v", err) + return + } + userPath = fmt.Sprintf("%s:%s", targetTempDirectory, userPath) + + targetArchitecture, err := myTarget.GetArchitecture() + if err != nil { + err = fmt.Errorf("error getting target architecture: %v", err) + return + } + // for each script we will run + for _, script := range scripts { + // add lkms to list of lkms to install + for _, lkm := range script.Lkms { + lkmsToInstall[lkm] = 1 + } + // add dependencies to list of dependencies to copy to target + for _, dependency := range script.Depends { + if len(script.Architectures) == 0 || util.StringInList(targetArchitecture, script.Architectures) { + dependenciesToCopy[path.Join(targetArchitecture, dependency)] = 1 + } + } + // add user's path to script + scriptWithPath := fmt.Sprintf("export PATH=\"%s\"\n%s", userPath, script.Script) + if script.Name == "" { + panic("script name cannot be empty") + } + scriptPath := path.Join(localTempDir, scriptNameToFilename(script.Name)) + // write script to local file + err = os.WriteFile(scriptPath, []byte(scriptWithPath), 0644) + if err != nil { + err = fmt.Errorf("error writing script to local file: %v", err) + return + } + // copy script to target + err = myTarget.PushFile(scriptPath, path.Join(targetTempDirectory, scriptNameToFilename(script.Name))) + if err != nil { + err = fmt.Errorf("error copying script to target: %v", err) + return + } + } + // copy dependencies to target + for dependency := range dependenciesToCopy { + var localDependencyPath string + // first look for the dependency in the "tools" directory + appDir := util.GetAppDir() + if util.Exists(path.Join(appDir, "tools", dependency)) { + localDependencyPath = path.Join(appDir, "tools", dependency) + } else { // not found in the tools directory, so extract it from resources + localDependencyPath, err = util.ExtractResource(Resources, path.Join("resources", dependency), localTempDir) + if err != nil { + if failIfDependencyNotFound { + err = fmt.Errorf("error extracting dependency: %v", err) + return + } + slog.Warn("failed to extract dependency", slog.String("dependency", dependency), slog.String("error", err.Error())) + err = nil + continue + } + } + // copy dependency to target + err = myTarget.PushFile(localDependencyPath, targetTempDirectory) + if err != nil { + err = fmt.Errorf("error copying dependency to target: %v", err) + return + } + } + // install lkms on target + var lkms []string + for lkm := range lkmsToInstall { + lkms = append(lkms, lkm) + } + if len(lkmsToInstall) > 0 { + installedLkms, err = myTarget.InstallLkms(lkms) + if err != nil { + err = fmt.Errorf("error installing LKMs: %v", err) + return + } + } + return +} diff --git a/internal/script/script_defs.go b/internal/script/script_defs.go new file mode 100644 index 0000000..423d712 --- /dev/null +++ b/internal/script/script_defs.go @@ -0,0 +1,980 @@ +package script + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +// script_defs.go defines the bash scripts that are used to collect information from target systems + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + HostnameScriptName = "hostname" + DateScriptName = "date" + DmidecodeScriptName = "dmidecode" + LscpuScriptName = "lscpu" + LspciBitsScriptName = "lspci bits" + LspciDevicesScriptName = "lspci devices" + LspciVmmScriptName = "lspci vmm" + UnameScriptName = "uname" + ProcCmdlineScriptName = "proc cmdline" + ProcCpuinfoScriptName = "proc cpuinfo" + EtcReleaseScriptName = "etc release" + GccVersionScriptName = "gcc version" + BinutilsVersionScriptName = "binutils version" + GlibcVersionScriptName = "glibc version" + PythonVersionScriptName = "python version" + Python3VersionScriptName = "python3 version" + JavaVersionScriptName = "java version" + OpensslVersionScriptName = "openssl version" + CpuidScriptName = "cpuid" + BaseFrequencyScriptName = "base frequency" + MaximumFrequencyScriptName = "maximum frequency" + ScalingDriverScriptName = "scaling driver" + ScalingGovernorScriptName = "scaling governor" + MaxCStateScriptName = "max c-state" + CstatesScriptName = "c-states" + SpecTurboFrequenciesScriptName = "spec turbo frequencies" + SpecTurboCoresScriptName = "spec turbo cores" + PPINName = "ppin" + PrefetchControlName = "prefetch control" + PrefetchersName = "prefetchers" + L3WaySizeName = "l3 way size" + PackagePowerLimitName = "package power limit" + EpbScriptName = "energy performance bias" + EppScriptName = "energy performance preference" + EppValidScriptName = "epp valid" + EppPackageScriptName = "energy performance preference package" + IaaDevicesScriptName = "iaa devices" + DsaDevicesScriptName = "dsa devices" + LshwScriptName = "lshw" + MemoryBandwidthAndLatencyScriptName = "memory bandwidth and latency" + NumaBandwidthScriptName = "numa bandwidth" + CpuSpeedScriptName = "cpu speed" + TurboFrequenciesScriptName = "turbo frequencies" + TurboFrequencyPowerAndTemperatureScriptName = "turbo frequency power and temperature" + IdlePowerScriptName = "idle power" + MpstatScriptName = "mpstat" + IostatScriptName = "iostat" + SarMemoryScriptName = "sar-memory" + SarNetworkScriptName = "sar-network" + TurbostatScriptName = "turbostat" + UncoreMaxFromMSRScriptName = "uncore max from msr" + UncoreMinFromMSRScriptName = "uncore min from msr" + UncoreMaxFromTPMIScriptName = "uncore max from tpmi" + UncoreMinFromTPMIScriptName = "uncore min from tpmi" + ElcScriptName = "efficiency latency control" + ChaCountScriptName = "cha count" + MeminfoScriptName = "meminfo" + TransparentHugePagesScriptName = "transparent huge pages" + NumaBalancingScriptName = "numa balancing" + NicInfoScriptName = "nic info" + DiskInfoScriptName = "disk info" + HdparmScriptName = "hdparm" + DfScriptName = "df" + FindMntScriptName = "findmnt" + CveScriptName = "cve" + ProcessListScriptName = "process list" + IpmitoolSensorsScriptName = "ipmitool sensors" + IpmitoolChassisScriptName = "ipmitool chassis" + IpmitoolEventsScriptName = "ipmitool events" + IpmitoolEventTimeScriptName = "ipmitool event time" + KernelLogScriptName = "kernel log" + PMUDriverVersionScriptName = "pmu driver version" + PMUBusyScriptName = "pmu busy" + ProfileJavaScriptName = "profile java" + ProfileSystemScriptName = "profile system" + GaudiInfoScriptName = "gaudi info" + GaudiFirmwareScriptName = "gaudi firmware" + GaudiNumaScriptName = "gaudi numa" +) + +const ( + x86_64 = "x86_64" +) + +// GetScriptByName returns the script definition with the given name. It will panic if the script is not found. +func GetScriptByName(name string) ScriptDefinition { + return GetTimedScriptByName(name, 0, 0, 0) +} + +// GetTimedScriptByName returns the script definition with the given name. It will panic if the script is not found. +func GetTimedScriptByName(name string, duration int, interval int, frequency int) ScriptDefinition { + for _, script := range getCollectionScripts(duration, interval, frequency) { + if script.Name == name { + return script + } + } + panic(fmt.Sprintf("script not found: %s", name)) +} + +// getCollectionScripts returns the script definitions that are used to collect information from the target system. +func getCollectionScripts(duration, interval int, frequency int) (scripts []ScriptDefinition) { + + // script definitions + scripts = []ScriptDefinition{ + // configuration scripts + { + Name: HostnameScriptName, + Script: "hostname", + }, + { + Name: DateScriptName, + Script: "date", + }, + { + Name: DmidecodeScriptName, + Script: "dmidecode", + Superuser: true, + Depends: []string{"dmidecode"}, + }, + { + Name: LscpuScriptName, + Script: "lscpu", + }, + { + Name: LspciBitsScriptName, + Script: "lspci -s $(lspci | grep 325b | awk 'NR==1{{print $1}}') -xxx | awk '$1 ~ /^90/{{print $9 $8 $7 $6; exit}}'", + Superuser: true, + Depends: []string{"lspci"}, + }, + { + Name: LspciDevicesScriptName, + Script: "lspci -d 8086:3258 | wc -l", + Depends: []string{"lspci"}, + }, + { + Name: LspciVmmScriptName, + Script: "lspci -vmm", + Depends: []string{"lspci"}, + }, + { + Name: UnameScriptName, + Script: "uname -a", + }, + { + Name: ProcCmdlineScriptName, + Script: "cat /proc/cmdline", + }, + { + Name: ProcCpuinfoScriptName, + Script: "cat /proc/cpuinfo", + }, + { + Name: EtcReleaseScriptName, + Script: "cat /etc/*-release", + }, + { + Name: GccVersionScriptName, + Script: "gcc --version", + }, + { + Name: BinutilsVersionScriptName, + Script: "ld -v", + }, + { + Name: GlibcVersionScriptName, + Script: "ldd --version", + }, + { + Name: PythonVersionScriptName, + Script: "python --version 2>&1", + }, + { + Name: Python3VersionScriptName, + Script: "python3 --version", + }, + { + Name: JavaVersionScriptName, + Script: "java -version 2>&1", + }, + { + Name: OpensslVersionScriptName, + Script: "openssl version", + }, + { + Name: CpuidScriptName, + Script: "cpuid -1", + Lkms: []string{"cpuid"}, + Depends: []string{"cpuid"}, + Superuser: true, + }, + { + Name: BaseFrequencyScriptName, + Script: "cat /sys/devices/system/cpu/cpu0/cpufreq/base_frequency", + }, + { + Name: MaximumFrequencyScriptName, + Script: "cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", + }, + { + Name: ScalingDriverScriptName, + Script: "cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver", + }, + { + Name: ScalingGovernorScriptName, + Script: "cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", + }, + { + Name: MaxCStateScriptName, + Script: "cat /sys/module/intel_idle/parameters/max_cstate", + }, + { + Name: CstatesScriptName, + Script: `# Directory where C-state information is stored +cstate_dir="/sys/devices/system/cpu/cpu0/cpuidle" + +# Check if the directory exists +if [ -d "$cstate_dir" ]; then + for state in "$cstate_dir"/state*; do + name=$(cat "$state/name") + disable=$(cat "$state/disable") + if [ "$disable" -eq 0 ]; then + status="Enabled" + else + status="Disabled" + fi + echo "$name,$status" + done +else + echo "C-state directory not found." +fi`, + }, + { + Name: SpecTurboCoresScriptName, + Script: "rdmsr 0x1ae", // MSR_TURBO_GROUP_CORE_CNT: Group Size of Active Cores for Turbo Mode Operation + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: SpecTurboFrequenciesScriptName, + Script: "rdmsr 0x1ad", // MSR_TURBO_RATIO_LIMIT: Maximum Ratio Limit of Turbo Mode + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: PPINName, + Script: "rdmsr -a 0x4f", // MSR_PPIN: Protected Processor Inventory Number + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: PrefetchControlName, + Script: "rdmsr -f 7:0 0x1a4", // MSR_PREFETCH_CONTROL: L2, DCU, and AMP Prefetchers enabled/disabled + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: PrefetchersName, + Script: "rdmsr 0x6d", // TODO: get name, used to read prefetchers + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: L3WaySizeName, + Script: "rdmsr 0xc90", // TODO: get name, used to read l3 size + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: PackagePowerLimitName, + Script: "rdmsr -f 14:0 0x610", // MSR_PKG_POWER_LIMIT: Package limit in bits 14:0 + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: EpbScriptName, + Script: "rdmsr -a -f 3:0 0x1B0", // IA32_ENERGY_PERF_BIAS: Energy Performance Bias Hint (0 is highest perf, 15 is highest energy saving) + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: EppValidScriptName, + Script: "rdmsr -a -f 60:60 0x774", // IA32_HWP_REQUEST: Energy Performance Preference, bit 60 indicates if per-cpu EPP is valid + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: EppScriptName, + Script: "rdmsr -a -f 31:24 0x774", // IA32_HWP_REQUEST: Energy Performance Preference, bits 24-31 (0 is highest perf, 255 is highest energy saving) + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: EppPackageScriptName, + Script: "rdmsr -f 31:24 0x772", // IA32_HWP_REQUEST_PKG: Energy Performance Preference, bits 24-31 (0 is highest perf, 255 is highest energy saving) + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: UncoreMaxFromMSRScriptName, + Script: "rdmsr -f 6:0 0x620", // MSR_UNCORE_RATIO_LIMIT: MAX_RATIO in bits 6:0 + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: UncoreMinFromMSRScriptName, + Script: "rdmsr -f 14:8 0x620", // MSR_UNCORE_RATIO_LIMIT: MAX_RATIO in bits 14:8 + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: UncoreMaxFromTPMIScriptName, + Script: "pcm-tpmi 2 0x18 -d -b 8:14", + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Depends: []string{"pcm-tpmi"}, + Superuser: true, + }, + { + Name: UncoreMinFromTPMIScriptName, + Script: "pcm-tpmi 2 0x18 -d -b 15:21", + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Depends: []string{"pcm-tpmi"}, + Superuser: true, + }, + { + Name: ElcScriptName, + Script: ` +# Script derived from bhs-power-mode script in Intel PCM repository +# Run the pcm-tpmi command to determine I/O and compute dies +output=$(pcm-tpmi 2 0x10 -d -b 26:26) + +# Parse the output to build lists of I/O and compute dies +io_dies=() +compute_dies=() +declare -A die_types +while read -r line; do + if [[ $line == *"instance 0"* ]]; then + die=$(echo "$line" | grep -oP 'entry \K[0-9]+') + if [[ $line == *"value 1"* ]]; then + die_types[$die]="IO" + io_dies+=("$die") + elif [[ $line == *"value 0"* ]]; then + die_types[$die]="Compute" + compute_dies+=("$die") + fi + fi +done <<< "$output" + +# Function to extract and calculate metrics from the value +extract_and_print_metrics() { + local value=$1 + local socket_id=$2 + local die=$3 + local die_type=${die_types[$die]} + + # Extract bits and calculate metrics + local min_ratio=$(( (value >> 15) & 0x7F )) + local max_ratio=$(( (value >> 8) & 0x7F )) + local eff_latency_ctrl_ratio=$(( (value >> 22) & 0x7F )) + local eff_latency_ctrl_low_threshold=$(( (value >> 32) & 0x7F )) + local eff_latency_ctrl_high_threshold=$(( (value >> 40) & 0x7F )) + local eff_latency_ctrl_high_threshold_enable=$(( (value >> 39) & 0x1 )) + + # Convert to MHz or percentage + min_ratio=$(( min_ratio * 100 )) + max_ratio=$(( max_ratio * 100 )) + eff_latency_ctrl_ratio=$(( eff_latency_ctrl_ratio * 100 )) + eff_latency_ctrl_low_threshold=$(( (eff_latency_ctrl_low_threshold * 100) / 127 )) + eff_latency_ctrl_high_threshold=$(( (eff_latency_ctrl_high_threshold * 100) / 127 )) + + # Print metrics + echo -n "$socket_id,$die,$die_type,$min_ratio,$max_ratio,$eff_latency_ctrl_ratio," + echo "$eff_latency_ctrl_low_threshold,$eff_latency_ctrl_high_threshold,$eff_latency_ctrl_high_threshold_enable" +} + +# Print CSV header +echo "Socket,Die,Type,Min Ratio (MHz),Max Ratio (MHz),ELC Ratio (MHz),ELC Low Threshold (%),ELC High Threshold (%),ELC High Threshold Enable" + +# Iterate over all dies and run pcm-tpmi for each to get the metrics +for die in "${!die_types[@]}"; do + output=$(pcm-tpmi 2 0x18 -d -e "$die") + + # Parse the output and extract metrics for each socket + while read -r line; do + if [[ $line == *"Read value"* ]]; then + value=$(echo "$line" | grep -oP 'value \K[0-9]+') + socket_id=$(echo "$line" | grep -oP 'instance \K[0-9]+') + extract_and_print_metrics "$value" "$socket_id" "$die" + fi + done <<< "$output" +done + `, + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Depends: []string{"pcm-tpmi"}, + Superuser: true, + }, + { + Name: ChaCountScriptName, + Script: `rdmsr 0x396 +rdmsr 0x702 +rdmsr 0x2FFE`, // uncore client cha count, uncore cha count, uncore cha count spr + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + Superuser: true, + }, + { + Name: IaaDevicesScriptName, + Script: "ls -1 /dev/iax", + }, + { + Name: DsaDevicesScriptName, + Script: "ls -1 /dev/dsa", + }, + { + Name: LshwScriptName, + Script: "lshw -businfo -numeric", + Depends: []string{"lshw"}, + Superuser: true, + }, + { + Name: MeminfoScriptName, + Script: "cat /proc/meminfo", + }, + { + Name: TransparentHugePagesScriptName, + Script: "cat /sys/kernel/mm/transparent_hugepage/enabled", + }, + { + Name: NumaBalancingScriptName, + Script: "cat /proc/sys/kernel/numa_balancing", + }, + { + Name: NicInfoScriptName, + Script: `lshw -businfo -numeric | grep -E "^(pci|usb).*? \S+\s+network\s+\S.*?" \ +| while read -r a ifc c ; do + ethtool "$ifc" + ethtool -i "$ifc" + echo -n "MAC Address: " + cat /sys/class/net/"$ifc"/address + echo -n "NUMA Node: " + cat /sys/class/net/"$ifc"/device/numa_node + echo -n "CPU Affinity: " + intlist=$( grep -e "$ifc" /proc/interrupts | cut -d':' -f1 | sed -e 's/^[[:space:]]*//' ) + for int in $intlist; do + cpu=$( cat /proc/irq/"$int"/smp_affinity_list ) + printf "%s:%s;" "$int" "$cpu" + done + printf "\n" + echo -n "IRQ Balance: " + pgrep irqbalance >/dev/null && echo "Enabled" || echo "Disabled" +done + `, + Depends: []string{"lshw"}, + Superuser: true, + }, + { + Name: DiskInfoScriptName, + Script: `echo "NAME|MODEL|SIZE|MOUNTPOINT|FSTYPE|RQ-SIZE|MIN-IO|FIRMWARE|ADDR|NUMA|LINKSPEED|LINKWIDTH|MAXLINKSPEED|MAXLINKWIDTH" +lsblk -r -o NAME,MODEL,SIZE,MOUNTPOINT,FSTYPE,RQ-SIZE,MIN-IO -e7 -e1 \ +| cut -d' ' -f1,2,3,4,5,6,7 --output-delimiter='|' \ +| while IFS='|' read -r name model size mountpoint fstype rqsize minio ; +do + # skip the lsblk output header + if [ "$name" = "NAME" ] ; then + continue + fi + fw="" + addr="" + numa="" + curlinkspeed="" + curlinkwidth="" + maxlinkspeed="" + maxlinkwidth="" + # replace \x20 with space in model + model=${model//\\x20/ } + # if name refers to an NVMe device e.g, nvme0n1 - nvme99n99 + if [[ $name =~ ^(nvme[0-9]+)n[0-9]+$ ]]; then + # get the name without the namespace + nvme=${BASH_REMATCH[1]} + if [ -f /sys/block/"$name"/device/firmware_rev ] ; then + fw=$( cat /sys/block/"$name"/device/firmware_rev ) + fi + if [ -f /sys/block/"$name"/device/address ] ; then + addr=$( cat /sys/block/"$name"/device/address ) + fi + if [ -d "/sys/block/$name/device/${nvme}" ]; then + numa=$( cat /sys/block/"$name"/device/"${nvme}"/numa_node ) + curlinkspeed=$( cat /sys/block/"$name"/device/"${nvme}"/device/current_link_speed ) + curlinkwidth=$( cat /sys/block/"$name"/device/"${nvme}"/device/current_link_width ) + maxlinkspeed=$( cat /sys/block/"$name"/device/"${nvme}"/device/max_link_speed ) + maxlinkwidth=$( cat /sys/block/"$name"/device/"${nvme}"/device/max_link_width ) + elif [ -d "/sys/block/$name/device/device" ]; then + numa=$( cat /sys/block/"$name"/device/device/numa_node ) + curlinkspeed=$( cat /sys/block/"$name"/device/device/current_link_speed ) + curlinkwidth=$( cat /sys/block/"$name"/device/device/current_link_width ) + maxlinkspeed=$( cat /sys/block/"$name"/device/device/max_link_speed ) + maxlinkwidth=$( cat /sys/block/"$name"/device/device/max_link_width ) + fi + fi + echo "$name|$model|$size|$mountpoint|$fstype|$rqsize|$minio|$fw|$addr|$numa|$curlinkspeed|$curlinkwidth|$maxlinkspeed|$maxlinkwidth" +done`, + }, + { + Name: HdparmScriptName, + Script: `lsblk -d -r -o NAME -e7 -e1 -n | while read -r device ; do + hdparm -i /dev/"$device" +done`, + Superuser: true, + }, + { + Name: DfScriptName, + Script: `df -h`, + }, + { + Name: FindMntScriptName, + Script: `findmnt -r`, + Superuser: true, + }, + { + Name: CveScriptName, + Script: "spectre-meltdown-checker.sh --batch text", + Superuser: true, + Lkms: []string{"msr"}, + Depends: []string{"spectre-meltdown-checker.sh", "rdmsr"}, + }, + { + Name: ProcessListScriptName, + Script: `ps -eo pid,ppid,%cpu,%mem,rss,command --sort=-%cpu,-pid | grep -v "]" | head -n 20`, + Sequential: true, + }, + { + Name: IpmitoolSensorsScriptName, + Script: "LC_ALL=C ipmitool sdr list full", + Superuser: true, + Depends: []string{"ipmitool"}, + }, + { + Name: IpmitoolChassisScriptName, + Script: "LC_ALL=C ipmitool chassis status", + Superuser: true, + Depends: []string{"ipmitool"}, + }, + { + Name: IpmitoolEventsScriptName, + Script: `LC_ALL=C ipmitool sel elist | tail -n20 | cut -d'|' -f2-`, + Superuser: true, + Lkms: []string{"ipmi_devintf", "ipmi_si"}, + Depends: []string{"ipmitool"}, + }, + { + Name: IpmitoolEventTimeScriptName, + Script: "LC_ALL=C ipmitool sel time get", + Superuser: true, + Depends: []string{"ipmitool"}, + }, + { + Name: KernelLogScriptName, + Script: "dmesg --kernel --human --nopager | tail -n20", + Superuser: true, + }, + { + Name: PMUDriverVersionScriptName, + Script: `dmesg | grep -A 1 "Intel PMU driver" | tail -1 | awk '{print $NF}'`, + Superuser: true, + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + }, + { + Name: PMUBusyScriptName, + Script: `# loop through the PMU counters and check if they are active or inactive +for i in 0x30a 0x309 0x30b 0x30c 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7 0xc8; do + arr=() + # read the value of the msr represented by the hex value 6 times, save results in an array + for j in {1..6}; do + val=$(rdmsr $i | tr -d '\n') + # if the value isn't a hex value, go on to next hex value + if [[ ! $val =~ ^[0-9a-fA-F]+$ ]]; then + echo "$i Unknown" + continue 2 + fi + arr+=($val) + done + # if the first and last value in the array are the same, the counter is inactive + if [ ${arr[0]} == ${arr[5]} ]; then + echo "$i Inactive" + else + echo "$i Active" + fi +done`, + Superuser: true, + Architectures: []string{x86_64}, + Families: []string{"6"}, // Intel + Lkms: []string{"msr"}, + Depends: []string{"rdmsr"}, + }, + { + Name: GaudiInfoScriptName, + Script: `hl-smi -Q module_id,serial,bus_id,driver_version -f csv`, + Architectures: []string{"x86_64"}, + Families: []string{"6"}, // Intel + }, + { + Name: GaudiFirmwareScriptName, + Script: `hl-smi --fw-version`, + Architectures: []string{"x86_64"}, + Families: []string{"6"}, // Intel + }, + { + Name: GaudiNumaScriptName, + Script: `hl-smi topo -N`, + Architectures: []string{"x86_64"}, + Families: []string{"6"}, // Intel + }, + // benchmarking scripts + { + Name: MemoryBandwidthAndLatencyScriptName, + Script: `# measure memory loaded latency +# need at least 2 GB (2,097,152 KB) of huge pages per NUMA node +min_kb=2097152 +numa_nodes=$( lscpu | grep "NUMA node(s):" | awk '{print $3}' ) +size_huge_pages_kb=$( cat /proc/meminfo | grep Hugepagesize | awk '{print $2}' ) +orig_num_huge_pages=$( cat /proc/sys/vm/nr_hugepages ) +needed_num_huge_pages=$( echo "$numa_nodes * $min_kb / $size_huge_pages_kb" | bc ) +if [ $needed_num_huge_pages -gt $orig_num_huge_pages ]; then + echo $needed_num_huge_pages > /proc/sys/vm/nr_hugepages +fi +mlc --loaded_latency +echo $orig_num_huge_pages > /proc/sys/vm/nr_hugepages`, + Architectures: []string{x86_64}, + Superuser: true, + Lkms: []string{"msr"}, + Depends: []string{"mlc"}, + Sequential: true, + }, + { + Name: NumaBandwidthScriptName, + Script: `# measure memory bandwidth matrix +# need at least 2 GB (2,097,152 KB) of huge pages per NUMA node +min_kb=2097152 +numa_nodes=$( lscpu | grep "NUMA node(s):" | awk '{print $3}' ) +size_huge_pages_kb=$( cat /proc/meminfo | grep Hugepagesize | awk '{print $2}' ) +orig_num_huge_pages=$( cat /proc/sys/vm/nr_hugepages ) +needed_num_huge_pages=$( echo "$numa_nodes * $min_kb / $size_huge_pages_kb" | bc ) +if [ $needed_num_huge_pages -gt $orig_num_huge_pages ]; then + echo $needed_num_huge_pages > /proc/sys/vm/nr_hugepages +fi +mlc --bandwidth_matrix +echo $orig_num_huge_pages > /proc/sys/vm/nr_hugepages`, + Architectures: []string{x86_64}, + Superuser: true, + Lkms: []string{"msr"}, + Depends: []string{"mlc"}, + Sequential: true, + }, + { + Name: CpuSpeedScriptName, + Script: `methods=$( stress-ng --cpu 1 --cpu-method x 2>&1 | cut -d":" -f2 | cut -c 6- ) +for method in $methods; do + printf "%s " "$method" + stress-ng --cpu 0 -t 1 --cpu-method "$method" --metrics-brief 2>&1 | tail -1 | awk '{print $9}' +done`, + Superuser: false, + Depends: []string{"stress-ng"}, + Sequential: true, + }, + { + Name: TurboFrequenciesScriptName, + Script: `# Function to expand a range of numbers, e.g. "0-24", into an array of numbers +expand_range() { + local range=$1 + local expanded=() + IFS=',' read -ra parts <<< "$range" + for part in "${parts[@]}"; do + if [[ $part == *-* ]]; then + IFS='-' read -ra limits <<< "$part" + for ((i=${limits[0]}; i<=${limits[1]}; i++)); do + expanded+=("$i") + done + else + expanded+=("$part") + fi + done + echo "${expanded[@]}" +} + +# Get the number of NUMA nodes and sockets +num_nodes=$(lscpu | grep 'NUMA node(s):' | awk '{print $3}') +num_sockets=$(lscpu | grep 'Socket(s):' | awk '{print $2}') + +# echo "Number of NUMA nodes: $num_nodes" +# echo "Number of sockets: $num_sockets" + +# Calculate the number of NUMA nodes per socket +nodes_per_socket=$((num_nodes / num_sockets)) + +# Array to hold the expanded core lists for each NUMA node +declare -a core_lists + +# Loop through each NUMA node in the first socket and expand the core IDs +for ((i=0; i max_length )); then + max_length=${#core_array[@]} + fi +done + +# Interleave the core IDs +for ((i=0; i/dev/null &) ; stress-ng --cpu 1 -t 20s 2>&1 ; stress-ng --cpu 0 -t 60s 2>&1 ; pkill -9 -f turbostat) | awk '$0~"stress" {print $0} $1=="Package" || $1=="CPU" || $1=="Core" || $1=="Node" {if(f!=1) print $0;f=1} $1=="-" {print $0}' `, + Superuser: true, + Lkms: []string{"msr"}, + Depends: []string{"turbostat", "stress-ng"}, + Sequential: true, + }, + { + Name: IdlePowerScriptName, + Script: `turbostat --show PkgWatt -n 1 | sed -n 2p`, + Superuser: true, + Lkms: []string{"msr"}, + Depends: []string{"turbostat"}, + Sequential: true, + }, + // telemetry scripts + { + Name: MpstatScriptName, + Script: func() string { + var count string + if duration != 0 && interval != 0 { + countInt := duration / interval + count = strconv.Itoa(countInt) + } + return fmt.Sprintf(`mpstat -u -T -I SCPU -P ALL %d %s`, interval, count) + }(), + Superuser: true, + Lkms: []string{}, + Depends: []string{"mpstat"}, + }, + { + Name: IostatScriptName, + Script: func() string { + var count string + if duration != 0 && interval != 0 { + countInt := duration / interval + count = strconv.Itoa(countInt) + } + return fmt.Sprintf(`S_TIME_FORMAT=ISO iostat -d -t %d %s | sed '/^loop/d'`, interval, count) + }(), + Superuser: true, + Lkms: []string{}, + Depends: []string{"iostat"}, + }, + { + Name: SarMemoryScriptName, + Script: func() string { + var count string + if duration != 0 && interval != 0 { + countInt := duration / interval + count = strconv.Itoa(countInt) + } + return fmt.Sprintf(`sar -r %d %s`, interval, count) + }(), + Superuser: true, + Lkms: []string{}, + Depends: []string{"sar", "sadc"}, + }, + { + Name: SarNetworkScriptName, + Script: func() string { + var count string + if duration != 0 && interval != 0 { + countInt := duration / interval + count = strconv.Itoa(countInt) + } + return fmt.Sprintf(`sar -n DEV %d %s`, interval, count) + }(), + Superuser: true, + Lkms: []string{}, + Depends: []string{"sar", "sadc"}, + }, + { + Name: TurbostatScriptName, + Script: func() string { + var count string + if duration != 0 && interval != 0 { + countInt := duration / interval + count = "-n " + strconv.Itoa(countInt) + } + return fmt.Sprintf(`turbostat -S -s PkgWatt,RAMWatt -q -i %d %s`, interval, count) + ` | awk '{ print strftime("%H:%M:%S"), $0 }'` + }(), + Superuser: true, + Lkms: []string{"msr"}, + Depends: []string{"turbostat"}, + }, + + // flamegraph scripts + { + Name: ProfileJavaScriptName, + Script: func() string { + apInterval := 0 + if frequency > 0 { + apInterval = int(1 / float64(frequency) * 1000000000) + } + return fmt.Sprintf(`# JAVA app call stack collection (run in background) +ap_interval=%d +duration=%d +declare -a java_pids=() +declare -a java_cmds=() +for pid in $( pgrep java ) ; do + # verify pid is still running + if [ -d "/proc/$pid" ]; then + java_pids+=($pid) + java_cmds+=("$( tr '\000' ' ' < /proc/$pid/cmdline )") + # profile pid in background + async-profiler/profiler.sh start -i "$ap_interval" -o collapsed "$pid" + fi +done +sleep $duration +# stop java profiling for each java pid +for idx in "${!java_pids[@]}"; do + pid="${java_pids[$idx]}" + cmd="${java_cmds[$idx]}" + echo "########## async-profiler $pid $cmd ##########" + async-profiler/profiler.sh stop -o collapsed "$pid" +done +`, apInterval, duration) + }(), + Superuser: true, + Depends: []string{"async-profiler"}, + }, + { + Name: ProfileSystemScriptName, + Script: func() string { + return fmt.Sprintf(`# system-wide call stack collection +# adjust perf_event_paranoid and kptr_restrict +PERF_EVENT_PARANOID=$( cat /proc/sys/kernel/perf_event_paranoid ) +echo -1 >/proc/sys/kernel/perf_event_paranoid +KPTR_RESTRICT=$( cat /proc/sys/kernel/kptr_restrict ) +echo 0 >/proc/sys/kernel/kptr_restrict +# system-wide call stack collection - frame pointer mode +frequency=%d +duration=%d +perf record -F $frequency -a -g -o perf_fp.data -m 129 -- sleep $duration & +PERF_FP_PID=$! +# system-wide call stack collection - dwarf mode +perf record -F $frequency -a -g -o perf_dwarf.data -m 257 --call-graph dwarf,8192 -- sleep $duration & +PERF_SYS_PID=$! +# wait for perf to finish +wait ${PERF_FP_PID} +wait ${PERF_SYS_PID} +# restore perf_event_paranoid and kptr_restrict +echo "$PERF_EVENT_PARANOID" > /proc/sys/kernel/perf_event_paranoid +echo "$KPTR_RESTRICT" > /proc/sys/kernel/kptr_restrict +# collapse perf data +perf script -i perf_dwarf.data | stackcollapse-perf.pl > perf_dwarf.folded +perf script -i perf_fp.data | stackcollapse-perf.pl > perf_fp.folded +if [ -f "perf_dwarf.folded" ]; then + echo "########## perf_dwarf ##########" + cat perf_dwarf.folded +fi +if [ -f "perf_fp.folded" ]; then + echo "########## perf_fp ##########" + cat perf_fp.folded +fi +`, frequency, duration) + }(), + Superuser: true, + Depends: []string{"perf", "stackcollapse-perf.pl"}, + }, + } + + // validate script definitions + var scriptNames = make(map[string]bool) + for i, s := range scripts { + if _, ok := scriptNames[s.Name]; ok { + panic(fmt.Sprintf("script %d, duplicate script name: %s", i, s.Name)) + } + if s.Name == "" { + panic(fmt.Sprintf("script %d, name cannot be empty", i)) + } + if s.Script == "" { + panic(fmt.Sprintf("script %d, script cannot be empty: %s", i, s.Name)) + } + if strings.ContainsAny(s.Name, "/") { + panic(fmt.Sprintf("script %d, name cannot contain /: %s", i, s.Name)) + } + } + return +} diff --git a/internal/script/script_test.go b/internal/script/script_test.go new file mode 100644 index 0000000..3b33457 --- /dev/null +++ b/internal/script/script_test.go @@ -0,0 +1,153 @@ +package script + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "os" + "regexp" + "strings" + "testing" + + "perfspect/internal/target" +) + +func TestRunScript(t *testing.T) { + var targets []target.Target + // targets = append(targets, target.NewRemoteTarget("", "emr", "", "", "", "", "../../tools/bin/sshpass", "")) + targets = append(targets, target.NewLocalTarget()) + for _, tgt := range targets { + targetTempDir, err := tgt.CreateTempDirectory("/tmp") + defer tgt.RemoveDirectory(targetTempDir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + var superuserVals []bool + //superuserVals = append(superuserVals, true) + superuserVals = append(superuserVals, false) + for _, superuser := range superuserVals { + // test one line script + scriptDef1 := ScriptDefinition{ + Name: "unittest hello", + Script: "echo 'Hello, World!'", + Superuser: superuser, + Lkms: []string{}, + Depends: []string{}, + Timeout: 0, + } + tempDir, err := os.MkdirTemp(os.TempDir(), "test") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + scriptOutput, err := RunScript(tgt, scriptDef1, tempDir) + os.RemoveAll(tempDir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectedStdout := "Hello, World!\n" + if scriptOutput.Stdout != expectedStdout { + t.Errorf("unexpected stdout: got %q, want %q", scriptOutput.Stdout, expectedStdout) + } + + expectedStderr := "" + if scriptOutput.Stderr != expectedStderr { + t.Errorf("unexpected stderr: got %q, want %q", scriptOutput.Stderr, expectedStderr) + } + + expectedExitCode := 0 + if scriptOutput.Exitcode != expectedExitCode { + t.Errorf("unexpected exit code: got %d, want %d", scriptOutput.Exitcode, expectedExitCode) + } + + // test multi-line script + scriptDef2 := ScriptDefinition{ + Name: "unittest cores", + Script: `num_cores_per_socket=$( lscpu | grep 'Core(s) per socket:' | head -1 | awk '{print $4}' ) +echo "Core Count: $num_cores_per_socket"`, + Superuser: superuser, + Lkms: []string{}, + Depends: []string{}, + Timeout: 0, + } + tempDir, err = os.MkdirTemp(os.TempDir(), "test") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + scriptOutput, err = RunScript(tgt, scriptDef2, tempDir) + os.RemoveAll(tempDir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + re := regexp.MustCompile("Core Count: [0-9]+") + if !re.MatchString(scriptOutput.Stdout) { + t.Errorf("unexpected stdout: got %q, want %q", scriptOutput.Stdout, "Core Count: [0-9]+") + } + + expectedStderr = "" + if scriptOutput.Stderr != expectedStderr { + t.Errorf("unexpected stderr: got %q, want %q", scriptOutput.Stderr, expectedStderr) + } + + expectedExitCode = 0 + if scriptOutput.Exitcode != expectedExitCode { + t.Errorf("unexpected exit code: got %d, want %d", scriptOutput.Exitcode, expectedExitCode) + } + + if false { + // test multi-line script w/ dependency + scriptDef3 := ScriptDefinition{ + Name: "Test Script", + Script: `count=1 +mpstat -u -T -I SCPU -P ALL 1 $count`, + Superuser: superuser, + Lkms: []string{}, + Depends: []string{"mpstat"}, + Timeout: 0, + } + tempDir, err := os.MkdirTemp(os.TempDir(), "test") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + scriptOutput, err = RunScript(tgt, scriptDef3, tempDir) + os.RemoveAll(tempDir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectedStdout = "Linux" + if !strings.HasPrefix(scriptOutput.Stdout, expectedStdout) { + t.Errorf("unexpected stdout: got %q, want %q", scriptOutput.Stdout, expectedStdout) + } + + expectedStderr = "" + if scriptOutput.Stderr != expectedStderr { + t.Errorf("unexpected stderr: got %q, want %q", scriptOutput.Stderr, expectedStderr) + } + + expectedExitCode = 0 + if scriptOutput.Exitcode != expectedExitCode { + t.Errorf("unexpected exit code: got %d, want %d", scriptOutput.Exitcode, expectedExitCode) + } + } + scriptDef1.Sequential = false + scriptDef2.Sequential = false + scriptOutputs, err := RunScripts(tgt, []ScriptDefinition{scriptDef1, scriptDef2}, false, os.TempDir()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(scriptOutputs) != 2 { + t.Fatalf("unexpected number of script outputs: got %d, want %d", len(scriptOutputs), 2) + } + expectedStdout = "Hello, World!\n" + if scriptOutputs["unittest hello"].Stdout != expectedStdout { + t.Errorf("unexpected stdout: got %q, want %q", scriptOutputs["unittest hello"].Stdout, expectedStdout) + } + re = regexp.MustCompile("Core Count: [0-9]+") + if !re.MatchString(scriptOutput.Stdout) { + t.Errorf("unexpected stdout: got %q, want %q", scriptOutput.Stdout, "Core Count: [0-9]+") + } + } + } +} diff --git a/internal/target/target.go b/internal/target/target.go new file mode 100644 index 0000000..ca7cd33 --- /dev/null +++ b/internal/target/target.go @@ -0,0 +1,788 @@ +/* +Package target provides a way to interact with local and remote systems. +*/ +package target + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "log/slog" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "perfspect/internal/util" +) + +// Target represents a machine or system where commands can be run. +// Implementations of this interface should provide methods to run +// commands, check connectivity, elevate privileges, and other operations +// that depend on the specific type of target (e.g., local or remote). +type Target interface { + // CanConnect checks if a connection can be established with the target. + // It returns true if a connection can be established, false otherwise. + CanConnect() bool + + // CanElevatePrivileges checks if the current user can elevate privileges. + // It returns true if the user can elevate privileges, false otherwise. + CanElevatePrivileges() bool + + // GetArchitecture returns the architecture of the target system. + // It returns a string representing the architecture and any error that occurred. + GetArchitecture() (arch string, err error) + + // GetFamily returns the family of the target system's CPU. + // It returns a string representing the family and any error that occurred. + GetFamily() (family string, err error) + + // GetModel returns the model of the target system's CPU. + // It returns a string representing the model and any error that occurred. + GetModel() (model string, err error) + + // GetName returns the name of the target system. + // It returns a string representing the host. + GetName() (name string) + + // GetUserPath returns the path of the current user on the target system. + // It returns a string representing the path and any error that occurred. + GetUserPath() (path string, err error) + + // RunCommand runs the specified command on the target. + // It returns the standard output, standard error, exit code, and any error that occurred. + RunCommand(cmd *exec.Cmd, timeout int) (stdout string, stderr string, exitCode int, err error) + + // RunCommandAsync runs the specified command on the target in an asynchronous manner. + // It returns any error that occurred. + RunCommandAsync(cmd *exec.Cmd, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, timeout int, cmdChannel chan *exec.Cmd) error + + // PushFile transfers a file from the local system to the target. + // It returns any error that occurred. + PushFile(srcPath string, dstPath string) error + + // PullFile transfers a file from the target to the local system. + // It returns any error that occurred. + PullFile(srcPath string, dstDir string) error + + // CreateDirectory creates a directory on the target at the specified path with the specified permissions. + // It returns the path of the created directory and any error that occurred. + CreateDirectory(baseDir string, targetDir string) (dir string, err error) + + // CreateTempDirectory creates a temporary directory on the target with the specified prefix. + // It returns the path of the created directory and any error that occurred. + CreateTempDirectory(rootDir string) (tempDir string, err error) + + // GetTempDirectory returns the path of the temporary directory on the target. It will be + // empty if the temporary directory has not been created yet. + GetTempDirectory() string + + // RemoveDirectory removes a directory from the target at the specified path. + // It returns any error that occurred. + RemoveDirectory(targetDir string) error + + // InstallLkms installs the specified Linux Kernel Modules (LKMs) on the target. + // It returns a list of installed LKMs and any error that occurred. + InstallLkms(lkms []string) (installedLkms []string, err error) + + // UninstallLkms uninstalls the specified Linux Kernel Modules (LKMs) from the target. + // It returns any error that occurred. + UninstallLkms(lkms []string) error +} + +type LocalTarget struct { + host string + sudo string + tempDir string + arch string + family string + model string + userPath string + canElevate int // zero indicates unknown, 1 indicates yes, -1 indicates no +} + +type RemoteTarget struct { + name string + host string + port string + user string + key string + sshPass string + sshpassPath string + tempDir string + arch string + family string + model string + userPath string + canElevate int +} + +// NewLocalTarget creates a new LocalTarget +func NewLocalTarget() *LocalTarget { + hostName, err := os.Hostname() + if err != nil { + hostName = "localhost" + } + t := &LocalTarget{ + host: hostName, + } + return t +} + +// NewRemoteTarget creates a new RemoteTarget instance with the provided parameters. +// It initializes the RemoteTarget struct and returns a pointer to it. +func NewRemoteTarget(name string, host string, port string, user string, key string) *RemoteTarget { + t := &RemoteTarget{ + name: name, + host: host, + port: port, + user: user, + key: key, + } + return t +} + +// SetSudo sets the sudo password for the target (LocalTarget only). +// Also sets the canElevate field to 0 to indicate that the sudo password has not been verified. +func (t *LocalTarget) SetSudo(sudo string) { + t.sudo = sudo + t.canElevate = 0 +} + +// SetSshPassPath sets the path to the sshpass binary (RemoteTarget only). +func (t *RemoteTarget) SetSshPassPath(sshpassPath string) { + t.sshpassPath = sshpassPath +} + +// SetSshPass sets the ssh password for the target (RemoteTarget only). +func (t *RemoteTarget) SetSshPass(sshPass string) { + t.sshPass = sshPass +} + +// RunCommand executes the given command with a timeout and returns the standard output, +// standard error, exit code, and any error that occurred. +func (t *LocalTarget) RunCommand(cmd *exec.Cmd, timeout int) (stdout string, stderr string, exitCode int, err error) { + input := "" + if t.sudo != "" && len(cmd.Args) > 2 && cmd.Args[0] == "sudo" && strings.HasPrefix(cmd.Args[1], "-") && strings.Contains(cmd.Args[1], "S") { // 'sudo -S' gets password from stdin + input = t.sudo + "\n" + } + return runLocalCommandWithInputWithTimeout(cmd, input, timeout) +} + +func (t *RemoteTarget) RunCommand(cmd *exec.Cmd, timeout int) (stdout string, stderr string, exitCode int, err error) { + localCommand := t.prepareLocalCommand(cmd, false) + return runLocalCommandWithInputWithTimeout(localCommand, "", timeout) +} + +// RunCommandAsync runs the given command asynchronously on the target. +// It sends the command to the cmdChannel and executes it with a timeout. +// The output from the command is sent to the stdoutChannel and stderrChannel, +// and the exit code is sent to the exitcodeChannel. +// The timeout parameter specifies the maximum time allowed for the command to run. +// Returns an error if there was a problem running the command. +func (t *LocalTarget) RunCommandAsync(cmd *exec.Cmd, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, timeout int, cmdChannel chan *exec.Cmd) (err error) { + localCommand := cmd + cmdChannel <- localCommand + err = runLocalCommandWithInputWithTimeoutAsync(localCommand, stdoutChannel, stderrChannel, exitcodeChannel, "", timeout) + return +} + +func (t *RemoteTarget) RunCommandAsync(cmd *exec.Cmd, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, timeout int, cmdChannel chan *exec.Cmd) (err error) { + localCommand := t.prepareLocalCommand(cmd, true) + cmdChannel <- localCommand + err = runLocalCommandWithInputWithTimeoutAsync(localCommand, stdoutChannel, stderrChannel, exitcodeChannel, "", timeout) + return +} + +// GetArchitecture returns the architecture of the target. +// It retrieves the architecture by calling the getArchitecture function. +func (t *LocalTarget) GetArchitecture() (arch string, err error) { + if t.arch == "" { + t.arch, err = getArchitecture(t) + } + return t.arch, err +} + +func (t *RemoteTarget) GetArchitecture() (arch string, err error) { + if t.arch == "" { + t.arch, err = getArchitecture(t) + } + return t.arch, err +} + +func (t *LocalTarget) GetFamily() (family string, err error) { + if t.family == "" { + t.family, err = getFamily(t) + } + return t.family, err +} + +func (t *RemoteTarget) GetFamily() (family string, err error) { + if t.family == "" { + t.family, err = getFamily(t) + } + return t.family, err +} + +func (t *LocalTarget) GetModel() (family string, err error) { + if t.model == "" { + t.model, err = getModel(t) + } + return t.model, err +} + +func (t *RemoteTarget) GetModel() (family string, err error) { + if t.model == "" { + t.model, err = getModel(t) + } + return t.model, err +} + +// CreateTempDirectory creates a temporary directory under the specified root directory. +// It returns the path of the created temporary directory and any error encountered. +func (t *LocalTarget) CreateTempDirectory(rootDir string) (tempDir string, err error) { + temp, err := os.MkdirTemp(rootDir, "perfspect.tmp.") + if err != nil { + return + } + tempDir, err = util.AbsPath(temp) + t.tempDir = tempDir + return +} + +func (t *RemoteTarget) CreateTempDirectory(rootDir string) (tempDir string, err error) { + var root string + if rootDir != "" { + root = fmt.Sprintf("--tmpdir=%s", rootDir) + } + cmd := exec.Command("mktemp", "-d", "-t", root, "perfspect.tmp.XXXXXXXXXX", "|", "xargs", "realpath") + tempDir, _, _, err = t.RunCommand(cmd, 0) + tempDir = strings.TrimSpace(tempDir) + t.tempDir = tempDir + return +} + +func (t *LocalTarget) GetTempDirectory() (tempDir string) { + return t.tempDir +} + +func (t *RemoteTarget) GetTempDirectory() (tempDir string) { + return t.tempDir +} + +// PushFile copies a file or directory from the source path to the destination path on the target. +// If the destination path is a directory, the file will be copied with the same name to that directory. +// If the destination path is a file, the file will be copied and overwritten. +// The file permissions of the source file will be preserved in the destination file. +func (t *LocalTarget) PushFile(srcPath string, dstPath string) (err error) { + srcFileStat, err := os.Stat(srcPath) + if err != nil { + return + } + if srcFileStat.IsDir() { + newDstDir := filepath.Join(dstPath, filepath.Base(srcPath)) + err = util.CreateIfNotExists(newDstDir, 0755) + if err != nil { + return + } + err = util.CopyDirectory(srcPath, newDstDir) + return + } + err = util.Copy(srcPath, dstPath) + return +} + +func (t *RemoteTarget) PushFile(srcPath string, dstDir string) error { + stdout, stderr, exitCode, err := t.prepareAndRunSCPCommand(srcPath, dstDir, true) + slog.Debug("push file", slog.String("srcPath", srcPath), slog.String("dstDir", dstDir), slog.String("stdout", stdout), slog.String("stderr", stderr), slog.Int("exitCode", exitCode)) + return err +} + +// PullFile pulls a file from the target's source path to the destination directory. +// It is a convenience method that internally calls the PushFile method. +func (t *LocalTarget) PullFile(srcPath string, dstDir string) error { + return t.PushFile(srcPath, dstDir) +} + +func (t *RemoteTarget) PullFile(srcPath string, dstDir string) error { + stdout, stderr, exitCode, err := t.prepareAndRunSCPCommand(srcPath, dstDir, false) + slog.Debug("pull file", slog.String("srcPath", srcPath), slog.String("dstDir", dstDir), slog.String("stdout", stdout), slog.String("stderr", stderr), slog.Int("exitCode", exitCode)) + return err +} + +// CreateDirectory creates a new directory under the specified base directory. +// It returns the full path of the created directory and any error encountered. +func (t *LocalTarget) CreateDirectory(baseDir string, targetDir string) (dir string, err error) { + dir = filepath.Join(baseDir, targetDir) + err = os.Mkdir(dir, 0764) + return +} + +func (t *RemoteTarget) CreateDirectory(baseDir string, targetDir string) (dir string, err error) { + dir = filepath.Join(baseDir, targetDir) + cmd := exec.Command("mkdir", dir) + _, _, _, err = t.RunCommand(cmd, 0) + return +} + +// RemoveDirectory removes the specified target directory. +// If the target directory is not empty, it will be deleted along with all its contents. +// The method returns an error if any error occurs during the removal process. +func (t *LocalTarget) RemoveDirectory(targetDir string) (err error) { + if targetDir != "" { + err = os.RemoveAll(targetDir) + } + return +} + +func (t *RemoteTarget) RemoveDirectory(targetDir string) (err error) { + if targetDir != "" { + cmd := exec.Command("rm", "-rf", targetDir) + _, _, _, err = t.RunCommand(cmd, 0) + } + return +} + +// CanConnect checks if the local target can establish a connection. +func (t *LocalTarget) CanConnect() bool { + return true +} + +func (t *RemoteTarget) CanConnect() bool { + cmd := exec.Command("exit", "0") + _, _, _, err := t.RunCommand(cmd, 5) + return err == nil +} + +// CanElevatePrivileges (on LocalTarget) checks if the user is root or sudo can be used to elevate privileges. +// It returns true if the user is root or if the sudo password works. +// If the `sudo` command is configured, it will attempt to run a command with sudo +// and check if the password works. If the passwordless sudo is configured, +// it will also check if passwordless sudo works. +// Returns true if the user can elevate privileges, false otherwise. +func (t *LocalTarget) CanElevatePrivileges() bool { + if t.canElevate != 0 { + return t.canElevate == 1 + } + if os.Geteuid() == 0 { + t.canElevate = 1 + return true // user is root + } + if t.sudo != "" { + cmd := exec.Command("sudo", "-kS", "ls") + stdin, _ := cmd.StdinPipe() + go func() { + defer stdin.Close() + io.WriteString(stdin, t.sudo+"\n") + }() + _, _, _, err := t.RunCommand(cmd, 0) + if err == nil { + t.canElevate = 1 + return true // sudo password works + } + } + cmd := exec.Command("sudo", "-kS", "ls") + _, _, _, err := t.RunCommand(cmd, 0) + if err == nil { // true - passwordless sudo works + t.canElevate = 1 + return true + } + t.canElevate = -1 + return false +} + +// CanElevatePrivileges (on RemoteTarget) checks if the user name is root or if sudo can be used to elevate privileges. +// Note that the sudo password is not used for this check. Password-less sudo is required. +func (t *RemoteTarget) CanElevatePrivileges() bool { + if t.canElevate != 0 { + return t.canElevate == 1 + } + if t.user == "root" { + t.canElevate = 1 + return true + } + cmd := exec.Command("sudo", "-kS", "ls") + _, _, _, err := t.RunCommand(cmd, 0) + if err == nil { // true - passwordless sudo works + t.canElevate = 1 + return true + } + t.canElevate = -1 + return false +} + +// InstallLkms installs the specified LKMs (Loadable Kernel Modules) on the target. +// It returns the list of installed LKMs and any error encountered during the installation process. +func (t *LocalTarget) InstallLkms(lkms []string) (installedLkms []string, err error) { + return installLkms(t, lkms) +} + +func (t *RemoteTarget) InstallLkms(lkms []string) (installedLkms []string, err error) { + return installLkms(t, lkms) +} + +// UninstallLkms uninstalls the specified LKMs (Loadable Kernel Modules) from the target. +// It takes a slice of strings representing the names of the LKMs to be uninstalled. +// It returns an error if any error occurs during the uninstallation process. +func (t *LocalTarget) UninstallLkms(lkms []string) (err error) { + return uninstallLkms(t, lkms) +} + +func (t *RemoteTarget) UninstallLkms(lkms []string) (err error) { + return uninstallLkms(t, lkms) +} + +// GetName returns the name of the Target. +func (t *LocalTarget) GetName() (host string) { + return t.host +} + +func (t *RemoteTarget) GetName() (host string) { + if t.name == "" { + return t.host + } + return t.name +} + +// GetUserPath returns the user's PATH environment variable after verifying that it only contains valid paths. +// It checks each path in the PATH environment variable and filters out any non-path strings. +// The function returns the verified paths joined by ":" as a string. +func (t *LocalTarget) GetUserPath() (string, error) { + if t.userPath == "" { + // get user's PATH environment variable, verify that it only contains paths (mitigate risk raised by Checkmarx) + var verifiedPaths []string + pathEnv := os.Getenv("PATH") + pathEnvPaths := strings.Split(pathEnv, ":") + for _, p := range pathEnvPaths { + files, err := filepath.Glob(p) + // Goal is to filter out any non path strings + // Glob will throw an error on pattern mismatch and return no files if no files + if err == nil && len(files) > 0 { + verifiedPaths = append(verifiedPaths, p) + } + } + t.userPath = strings.Join(verifiedPaths, ":") + } + return t.userPath, nil +} + +func (t *RemoteTarget) GetUserPath() (string, error) { + if t.userPath == "" { + cmd := exec.Command("echo", "$PATH") + stdout, _, _, err := t.RunCommand(cmd, 0) + if err != nil { + return "", err + } + t.userPath = strings.TrimSpace(stdout) + } + return t.userPath, nil +} + +// helpers below + +func runLocalCommandWithInputWithTimeout(cmd *exec.Cmd, input string, timeout int) (stdout string, stderr string, exitCode int, err error) { + logInput := "" + if input != "" { + logInput = "******" + } + slog.Debug("running local command", slog.String("cmd", cmd.String()), slog.String("input", logInput), slog.Int("timeout", timeout)) + if timeout > 0 { + var cancel context.CancelFunc + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + commandWithContext := exec.CommandContext(ctx, cmd.Path, cmd.Args[1:]...) + commandWithContext.Env = cmd.Env + cmd = commandWithContext + } + if input != "" { + cmd.Stdin = strings.NewReader(input) + } + var outbuf, errbuf strings.Builder + cmd.Stdout = &outbuf + cmd.Stderr = &errbuf + err = cmd.Run() + stdout = outbuf.String() + stderr = errbuf.String() + if err != nil { + exitError := &exec.ExitError{} + if errors.As(err, &exitError) { + exitCode = exitError.ExitCode() + } + } + return +} + +// TODO: does timeout make sense with async functions? +func runLocalCommandWithInputWithTimeoutAsync(cmd *exec.Cmd, stdoutChannel chan string, stderrChannel chan string, exitcodeChannel chan int, input string, timeout int) (err error) { + logInput := "" + if input != "" { + logInput = "******" + } + slog.Debug("running local command (async)", slog.String("cmd", cmd.String()), slog.String("input", logInput), slog.Int("timeout", timeout)) + if timeout > 0 { + var cancel context.CancelFunc + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + commandWithContext := exec.CommandContext(ctx, cmd.Path, cmd.Args[1:]...) + commandWithContext.Env = cmd.Env + cmd = commandWithContext + } + if input != "" { + cmd.Stdin = strings.NewReader(input) + } + stdoutReader, err := cmd.StdoutPipe() + if err != nil { + err = fmt.Errorf("failed to get stdout pipe: %v", err) + return + } + stdoutScanner := bufio.NewScanner(stdoutReader) + stderrReader, err := cmd.StderrPipe() + if err != nil { + err = fmt.Errorf("failed to get stderr pipe: %v", err) + return + } + stderrScanner := bufio.NewScanner(stderrReader) + if err = cmd.Start(); err != nil { + err = fmt.Errorf("failed to run command (%s): %v", cmd, err) + return + } + go func() { + for stdoutScanner.Scan() { + text := stdoutScanner.Text() + stdoutChannel <- text + } + }() + go func() { + for stderrScanner.Scan() { + text := stderrScanner.Text() + stderrChannel <- text + } + }() + err = cmd.Wait() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + exitcodeChannel <- exitError.ExitCode() + } else { + panic(fmt.Sprintf("err from cmd.Wait is not type exec.ExitError: %v", err)) + } + } else { + exitcodeChannel <- 0 + } + return nil +} + +func (t *RemoteTarget) prepareSSHFlags(scp bool, async bool, prompt bool) (flags []string) { + flags = []string{ + "-2", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "-o", + "ConnectTimeout=10", // This one exposes a bug in Windows' SSH client. Each connection takes + "-o", // 10 seconds to establish. https://github.com/PowerShell/Win32-OpenSSH/issues/1352 + "GSSAPIAuthentication=no", // This one is not supported, but is ignored on Windows. + "-o", + "ServerAliveInterval=30", + "-o", + "ServerAliveCountMax=10", // 30 * 10 = maximum 300 seconds before disconnect on no data + "-o", + "LogLevel=ERROR", + } + // turn on batch mode to avoid prompts for passwords + if !prompt { + promptFlags := []string{ + "-o", + "BatchMode=yes", + } + flags = append(flags, promptFlags...) + } + // when using a control master, a long-running remote program doesn't get terminated when the local ssh client is terminated + if !async { + controlPathFlags := []string{ + "-o", + "ControlPath=" + filepath.Join(os.TempDir(), `control-%h-%p-%r`), + "-o", + "ControlMaster=auto", + "-o", + "ControlPersist=1m", + } + flags = append(flags, controlPathFlags...) + } + if t.key != "" { + keyFlags := []string{ + "-o", + "PreferredAuthentications=publickey", + "-o", + "PasswordAuthentication=no", + "-i", + t.key, + } + flags = append(flags, keyFlags...) + } + if t.port != "" { + if scp { + flags = append(flags, "-P") + } else { + flags = append(flags, "-p") + } + flags = append(flags, t.port) + } + return +} + +func (t *RemoteTarget) prepareSSHCommand(command []string, async bool, prompt bool) []string { + var cmd []string + cmd = append(cmd, "ssh") + cmd = append(cmd, t.prepareSSHFlags(false, async, prompt)...) + if t.user != "" { + cmd = append(cmd, t.user+"@"+t.host) + } else { + cmd = append(cmd, t.host) + } + cmd = append(cmd, "--") + cmd = append(cmd, command...) + return cmd +} + +func (t *RemoteTarget) prepareSCPCommand(src string, dstDir string, push bool) []string { + var cmd []string + cmd = append(cmd, "scp") + cmd = append(cmd, t.prepareSSHFlags(true, false, false)...) + if push { + fileInfo, err := os.Stat(src) + if err != nil { + slog.Error("error getting file info", slog.String("src", src), slog.String("error", err.Error())) + return nil + } + if fileInfo.IsDir() { + cmd = append(cmd, "-r") + } + cmd = append(cmd, src) + dst := t.host + ":" + dstDir + if t.user != "" { + dst = t.user + "@" + dst + } + cmd = append(cmd, dst) + } else { // pull + s := t.host + ":" + src + if t.user != "" { + s = t.user + "@" + s + } + cmd = append(cmd, s) + cmd = append(cmd, dstDir) + } + return cmd +} + +func (t *RemoteTarget) prepareLocalCommand(cmd *exec.Cmd, async bool) *exec.Cmd { + var name string + var args []string + usePass := t.key == "" && t.sshPass != "" + sshCommand := t.prepareSSHCommand(cmd.Args, async, usePass) + if usePass { + name = t.sshpassPath + args = []string{"-e", "--"} + args = append(args, sshCommand...) + } else { + name = sshCommand[0] + args = sshCommand[1:] + } + localCommand := exec.Command(name, args...) + if usePass { + localCommand.Env = append(localCommand.Env, "SSHPASS="+t.sshPass) + } + return localCommand +} + +func (t *RemoteTarget) prepareAndRunSCPCommand(srcPath string, dstDir string, isPush bool) (stdout string, stderr string, exitCode int, err error) { + scpCommand := t.prepareSCPCommand(srcPath, dstDir, isPush) + var name string + var args []string + usePass := t.key == "" && t.sshPass != "" + if usePass { + name = t.sshpassPath + args = append(args, "-e", "--") + args = append(args, scpCommand...) + } else { + name = scpCommand[0] + args = scpCommand[1:] + } + localCommand := exec.Command(name, args...) + if usePass { + localCommand.Env = append(localCommand.Env, "SSHPASS="+t.sshPass) + } + stdout, stderr, exitCode, err = runLocalCommandWithInputWithTimeout(localCommand, "", 0) + return +} + +func getArchitecture(t Target) (arch string, err error) { + cmd := exec.Command("uname", "-m") + arch, _, _, err = t.RunCommand(cmd, 0) + if err != nil { + return + } + arch = strings.TrimSpace(arch) + return +} + +func getFamily(t Target) (family string, err error) { + cmd := exec.Command("bash", "-c", "lscpu | grep -i \"^CPU family:\" | awk '{print $NF}'") + family, _, _, err = t.RunCommand(cmd, 0) + if err != nil { + return + } + family = strings.TrimSpace(family) + return +} + +func getModel(t Target) (model string, err error) { + cmd := exec.Command("bash", "-c", "lscpu | grep -i model: | awk '{print $NF}'") + model, _, _, err = t.RunCommand(cmd, 0) + if err != nil { + return + } + model = strings.TrimSpace(model) + return +} + +func installLkms(t Target, lkms []string) (installedLkms []string, err error) { + if !t.CanElevatePrivileges() { + err = fmt.Errorf("can't elevate privileges; elevated privileges required to install lkms") + return + } + for _, lkm := range lkms { + slog.Debug("attempting to install kernel module", slog.String("lkm", lkm)) + _, _, _, err := t.RunCommand(exec.Command("modprobe", "--first-time", lkm), 10) + if err != nil { + slog.Debug("kernel module already installed or problem installing", slog.String("lkm", lkm), slog.String("error", err.Error())) + continue + } + slog.Debug("kernel module installed", slog.String("lkm", lkm)) + installedLkms = append(installedLkms, lkm) + } + return +} + +func uninstallLkms(t Target, lkms []string) (err error) { + if !t.CanElevatePrivileges() { + err = fmt.Errorf("can't elevate privileges; elevated privileges required to uninstall lkms") + return + } + for _, lkm := range lkms { + slog.Debug("attempting to uninstall kernel module", slog.String("lkm", lkm)) + _, _, _, err := t.RunCommand(exec.Command("modprobe", "-r", lkm), 10) + if err != nil { + slog.Error("error uninstalling kernel module", slog.String("lkm", lkm), slog.String("error", err.Error())) + continue + } + slog.Debug("kernel module uninstalled", slog.String("lkm", lkm)) + } + return +} diff --git a/internal/target/target_test.go b/internal/target/target_test.go new file mode 100644 index 0000000..16e799c --- /dev/null +++ b/internal/target/target_test.go @@ -0,0 +1,19 @@ +package target + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "testing" +) + +func TestNew(t *testing.T) { + localTarget := NewLocalTarget() + if localTarget == nil { + t.Fatal("failed to create a local target") + } + remoteTarget := NewRemoteTarget("label", "hostname", "22", "user", "key") + if remoteTarget == nil { + t.Fatal("failed to create a remote target") + } +} diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 0000000..2f35ad2 --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,405 @@ +/* +Package util includes utility/helper functions that may be useful to other modules. +*/ +package util + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "archive/tar" + "compress/gzip" + "embed" + "encoding/binary" + "fmt" + "io" + "io/fs" + "math" + "os" + "os/user" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +// ExpandUser expands '~' to user's home directory, if found, otherwise returns original path +func ExpandUser(path string) string { + usr, _ := user.Current() + if path == "~" { + return usr.HomeDir + } else if strings.HasPrefix(path, "~"+string(os.PathSeparator)) { + return filepath.Join(usr.HomeDir, path[2:]) + } else { + return path + } +} + +// AbsPath returns absolute path after expanding '~' to user's home dir +// Useful when application is started by a process that isn't a shell, e.g. PKB +// Use everywhere in place of filepath.Abs() +func AbsPath(path string) (string, error) { + return filepath.Abs(ExpandUser(path)) +} + +// FileExists checks if a file exists at the given path. +// It returns a boolean indicating whether the file exists, and an error if the +// path refers to a non-regular file, e.g., a directory. +func FileExists(path string) (exists bool, err error) { + var fileInfo fs.FileInfo + fileInfo, err = os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + exists = false + err = nil + return + } + return + } + if !fileInfo.Mode().IsRegular() { + err = fmt.Errorf("%s not a file", path) + return + } + exists = true + return +} + +// DirectoryExists checks if the specified directory exists. +// It returns a boolean indicating whether the directory exists and an error if the +// path refers to anything other than a directory, e.g., a regular file. +func DirectoryExists(path string) (exists bool, err error) { + var fileInfo fs.FileInfo + fileInfo, err = os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + exists = false + err = nil + return + } + return + } + if !fileInfo.Mode().IsDir() { + err = fmt.Errorf("%s not a directory", path) + return + } + exists = true + return +} + +// CopyDirectory copies the contents of a directory from the source path to the destination path. +// It recursively copies all subdirectories and files within the directory. +// The function returns an error if any error occurs during the copying process. +func CopyDirectory(scrDir, dest string) error { + entries, err := os.ReadDir(scrDir) + if err != nil { + return err + } + for _, entry := range entries { + sourcePath := filepath.Join(scrDir, entry.Name()) + destPath := filepath.Join(dest, entry.Name()) + fileInfo, err := os.Stat(sourcePath) + if err != nil { + return err + } + if fileInfo.Mode().IsDir() { + // Create the subdirectory in the destination directory + if err := CreateIfNotExists(destPath, 0755); err != nil { + return err + } + // Recursively copy the contents of the subdirectory + if err := CopyDirectory(sourcePath, destPath); err != nil { + return err + } + } else if fileInfo.Mode().IsRegular() { + // Copy the file to the destination directory + if err := Copy(sourcePath, destPath); err != nil { + return err + } + } + } + return nil +} + +// Copy copies a file from the source path to the destination path. +// If the destination path is a directory, the file will be copied with the same name to that directory. +// The file permissions of the source file will be preserved in the destination file. +func Copy(srcFile, dstFile string) error { + // Open the source file + srcFileStat, err := os.Stat(srcFile) + if err != nil { + return err + } + src, err := os.Open(srcFile) + if err != nil { + return err + } + defer src.Close() + // Create the destination file + dstFileStat, err := os.Stat(dstFile) + if err == nil && dstFileStat.IsDir() { + dstFile = filepath.Join(dstFile, filepath.Base(srcFile)) + } + dest, err := os.Create(dstFile) + if err != nil { + return err + } + // Copy the contents of the source file to the destination file + _, err = io.Copy(dest, src) + dest.Close() + if err != nil { + return err + } + // Preserve the file permissions of the source file in the destination file + err = os.Chmod(dstFile, srcFileStat.Mode()) + return err +} + +// Exists checks if a file or directory exists at the given file path. +// It returns true if the file or directory exists, and false otherwise. +func Exists(filePath string) bool { + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return false + } + return true +} + +// CreateIfNotExists creates a directory at the specified path if it does not already exist. +// If the directory already exists, it does nothing and returns nil. +// If there is an error while creating the directory, it returns an error with a descriptive message. +func CreateIfNotExists(dir string, perm os.FileMode) error { + if Exists(dir) { + return nil + } + if err := os.MkdirAll(dir, perm); err != nil { + return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error()) + } + return nil +} + +// StringIndexInList returns the index of the given string in the given list of +// strings and error if not found +func StringIndexInList(s string, l []string) (idx int, err error) { + var item string + for idx, item = range l { + if item == s { + return + } + } + err = fmt.Errorf("%s not found in %s", s, strings.Join(l, ", ")) + return +} + +// StringInList confirms if string is in list of strings +func StringInList(s string, l []string) bool { + for _, item := range l { + if item == s { + return true + } + } + return false +} + +// GeoMean calculates the geomean of a slice of floats +func GeoMean(vals []float64) (val float64) { + m := 0.0 + for i, x := range vals { + lx := math.Log(x) + m += (lx - m) / float64(i+1) + } + val = math.Exp(m) + return +} + +// ExtractResource extracts a resource from the given embed.FS and saves it to the specified temporary directory. +// It returns the path to the saved resource file and any error encountered during the process. +func ExtractResource(resources embed.FS, resourcePath string, tempDir string) (string, error) { + var outPath string + var resourceBytes []byte + isDir := false + resourceBytes, err := resources.ReadFile(resourcePath) + if err != nil { + if strings.Contains(err.Error(), "is a directory") { + isDir = true + } else { + return "", err + } + } + if isDir { + dirEntries, err := resources.ReadDir(resourcePath) + if err != nil { + return "", err + } + resourceName := filepath.Base(resourcePath) + outPath = filepath.Join(tempDir, resourceName) + err = os.Mkdir(outPath, 0755) + if err != nil { + return "", err + } + for _, entry := range dirEntries { + // Recursively extract resources from subdirectories + _, err = ExtractResource(resources, filepath.Join(resourcePath, entry.Name()), outPath) + if err != nil { + return "", err + } + } + } else { + // write the resource to a file in the temp directory + resourceName := filepath.Base(resourcePath) + outPath = filepath.Join(tempDir, resourceName) + var f *os.File + f, err = os.OpenFile(outPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0744) + if err != nil { + return "", err + } + defer f.Close() + err = binary.Write(f, binary.LittleEndian, resourceBytes) + if err != nil { + return "", err + } + } + return outPath, nil +} + +// UniqueAppend appends an item to a slice if it is not already present +func UniqueAppend(slice []string, item string) []string { + for _, s := range slice { + if s == item { + return slice + } + } + return append(slice, item) +} + +// CompareVersions compares two version strings +// version format: major.minor.patch<-alpha|beta|rc><.build> +// examples: 1.2.3, 1.2.3-alpha.4 +// Returns +// -1 if v1 is less than v2 +// 0 if v1 is equal to v2 +// 1 if v1 is greater than v2 +// An error if the version strings are not valid +func CompareVersions(v1, v2 string) (int, error) { + re := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)[-]?(alpha|beta|rc)?[\.]?(\d+)?`) + v1Parts := re.FindStringSubmatch(v1) + if v1Parts == nil { + return 0, fmt.Errorf("error: unable to parse version string: %s", v1) + } + v2Parts := re.FindStringSubmatch(v2) + if v2Parts == nil { + return 0, fmt.Errorf("error: unable to parse version string: %s", v2) + } + // compare version parts + for i := 1; i < 6; i++ { + if i == 4 { + v1Part := v1Parts[i] + v2Part := v2Parts[i] + // compare alpha, beta, rc + if v1Part == "" && v2Part == "" { + return 0, nil + } else if v1Part == "" && v2Part != "" { // v2 is tagged with alpha, beta, rc + return 1, nil + } else if v1Part != "" && v2Part == "" { // v1 is tagged with alpha, beta, rc + return -1, nil + } else { // both v1 and v2 are tagged with alpha, beta, rc + intVals := map[string]int{"alpha": 1, "beta": 2, "rc": 3} + if intVals[v1Part] > intVals[v2Part] { + return 1, nil + } else if intVals[v1Part] < intVals[v2Part] { + return -1, nil + } + } + continue + } + v1Part, err := strconv.Atoi(v1Parts[i]) + if err != nil { + return 0, err + } + v2Part, err := strconv.Atoi(v2Parts[i]) + if err != nil { + return 0, err + } + if v1Part > v2Part { + return 1, nil + } else if v1Part < v2Part { + return -1, nil + } + } + // The version strings are equal + return 0, nil +} + +// ExtractTGZ extracts the contents of a tarball (.tar.gz) file to the specified destination directory. +// If stripComponent is true, the first directory in the tarball will be skipped. +func ExtractTGZ(tarballPath, destDir string, stripComponent bool) error { + // Open the tarball + tarball, err := os.Open(tarballPath) + if err != nil { + return err + } + defer tarball.Close() + gzipReader, err := gzip.NewReader(tarball) + if err != nil { + return err + } + defer gzipReader.Close() + // Create a new tar reader + tarReader := tar.NewReader(gzipReader) + + targetIdx := 0 + firstDirectory := "" + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + // Check for invalid paths, there should never be a ".." in the path + if strings.Contains(header.Name, "..") { + return fmt.Errorf("tarball contains invalid path: %s", header.Name) + } + + target := filepath.Join(destDir, header.Name) + + if stripComponent { + // Skip the first directory in the tarball + if targetIdx == 0 && header.Typeflag != tar.TypeDir { + return fmt.Errorf("first entry in tarball is not a directory") + } + if targetIdx == 0 { + firstDirectory = header.Name + targetIdx++ + continue + } else if targetIdx > 0 { + // remove the first directory from the target path + target = filepath.Join(destDir, strings.TrimPrefix(header.Name, firstDirectory)) + } + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil { + return err + } + case tar.TypeReg: + f, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode)) + if err != nil { + return err + } + if _, err := io.Copy(f, tarReader); err != nil { + f.Close() + return err + } + f.Close() + } + targetIdx++ + } + return nil +} + +// GetAppDir returns the directory of the executable +func GetAppDir() string { + exePath, _ := os.Executable() + return filepath.Dir(exePath) +} diff --git a/internal/util/util_test.go b/internal/util/util_test.go new file mode 100644 index 0000000..ab1fe4d --- /dev/null +++ b/internal/util/util_test.go @@ -0,0 +1,34 @@ +package util + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import "testing" + +func TestCompareVersions(t *testing.T) { + tests := []struct { + v1 string + v2 string + expected int + }{ + {"1.0.0", "1.0.0", 0}, + {"1.0.0", "1.0.1", -1}, + {"1.0.1", "1.0.0", 1}, + {"1.0.0-alpha.1", "1.0.0-beta.1", -1}, + {"1.0.0-beta.1", "1.0.0-rc.1", -1}, + {"1.0.0-rc.1", "1.0.0", -1}, + {"1.0.0-alpha.1", "1.0.0-alpha.2", -1}, + {"1.0.0-alpha.2", "1.0.0-alpha.1", 1}, + {"1.0.0-alpha.1", "1.0.0-alpha.1", 0}, + } + + for _, test := range tests { + result, err := CompareVersions(test.v1, test.v2) + if err != nil { + t.Fatalf("failed to compare versions: %v", err) + } + if result != test.expected { + t.Errorf("expected %d, got %d for versions %s and %s", test.expected, result, test.v1, test.v2) + } + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..1801961 --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +package main + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import "perfspect/cmd" + +func main() { + cmd.Execute() +} diff --git a/perf-collect.py b/perf-collect.py deleted file mode 100644 index 46158a6..0000000 --- a/perf-collect.py +++ /dev/null @@ -1,659 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -import json -import logging -import os -import platform -import sys -import subprocess # nosec -import shlex # nosec -import time -from argparse import ArgumentParser -from src import perf_helpers -from src import prepare_perf_events as prep_events -from src.common import crash -from src import common - - -SUPPORTED_ARCHITECTURES = [ - "Broadwell", - "Skylake", - "Cascadelake", - "Icelake", - "SapphireRapids", - "EmeraldRapids", - "SierraForest", - "GraniteRapids", -] - - -# meta data gathering -def write_metadata( - outcsv, - collection_events, - arch, - cpuname, - cpuid_info, - pmu_driver_version, - fixed_tma_supported, - muxinterval, - cpu, - socket, - psi, -): - tsc_freq = str(perf_helpers.get_tsc_freq()) - data = "" - time_stamp = "" - validate_file(outcsv) - with open(outcsv, "r") as original: - time_stamp = original.readline() - data = original.read() - with open(outcsv, "w") as modified: - modified.write("### META DATA ###,\n") - modified.write("SYSTEM_TSC_FREQ (MHz)," + tsc_freq + ",\n") - modified.write("CORES_PER_SOCKET," + str(perf_helpers.get_cpu_count()) + ",\n") - modified.write("SOCKET_COUNT," + str(perf_helpers.get_socket_count()) + ",\n") - modified.write("HYPERTHREADING_ON," + str(perf_helpers.get_ht_status()) + ",\n") - counts = perf_helpers.get_unc_device_counts() - modified.write("IMC count," + str(counts["imc"]) + ",\n") - modified.write("CHAS_PER_SOCKET," + str(counts["cha"]) + ",\n") - modified.write("UPI count," + str(counts["upi"]) + ",\n") - modified.write("B2CMI count, " + str(counts["b2cmi"]) + ",\n") - modified.write("Architecture," + str(arch) + ",\n") - modified.write("Model," + str(cpuname) + ",\n") - modified.write("kernel version," + perf_helpers.get_version() + "\n") - for _socket, _cpus in cpuid_info.items(): - modified.write("Socket:" + str(_socket) + ",") - for c in _cpus: - modified.write(str(c) + ";") - modified.write("\n") - modified.write("PMUDriverVersion," + str(pmu_driver_version) + ",\n") - modified.write("FixedTMASupported," + str(fixed_tma_supported) + ",\n") - modified.write("Perf event mux Interval ms," + str(muxinterval) + ",\n") - cpumode = "enabled" if cpu else "disabled" - socketmode = "enabled" if socket else "disabled" - if args.cid is not None: - cgname = "enabled," - for cgroup in cgroups: - cgname += cgroup + "=" + cgroup.replace("/", "-") + "," - else: - cgname = "disabled" - modified.write("cgroups=" + str(cgname) + "\n") - - cpusets = "" - if args.cid is not None: - for cgroup in cgroups: - cgroup_paths = [ - "/sys/fs/cgroup/cpuset/" + cgroup + "/cpuset.cpus", # cgroup v1 - "/sys/fs/cgroup/" + cgroup + "/cpuset.cpus", # cgroup v2 - ] - cg_path_found = False - for path in cgroup_paths: - try: - with open(path, "r") as cpu_set_file: - cg_path_found = True - cpu_set = cpu_set_file.read() - cpu_set = cpu_set.strip() - cpu_set = cpu_set.replace(",", "+") - break - except FileNotFoundError: - continue - - if not cg_path_found or cpu_set == "": - # A missing path or an empty cpu-set in v2 indicates that the container is running on all CPUs - cpu_set = "0-" + str( - int( - perf_helpers.get_cpu_count() - * perf_helpers.get_socket_count() - * perf_helpers.get_ht_count() - - 1 - ) - ) - - cpusets += "," + cpu_set - else: - cpusets = ",disabled" - - modified.write("cpusets" + cpusets + ",\n") - modified.write("Percpu mode," + cpumode + ",\n") - modified.write("Persocket mode," + socketmode + ",\n") - modified.write("PSI," + json.dumps(psi) + "\n") - modified.write("PerfSpect version," + perf_helpers.get_tool_version() + ",\n") - modified.write("### PERF EVENTS ###" + ",\n") - for e in collection_events: - modified.write(e + "\n") - modified.write("\n") - modified.write("### PERF DATA ###" + ",\n") - if time_stamp: - zone = subprocess.check_output( # nosec - ["date", "+%Z"], universal_newlines=True # nosec - ).split() # nosec - epoch = str(perf_helpers.get_epoch(time_stamp)) - modified.write( - time_stamp.rstrip() + " " + zone[0] + " EPOCH " + epoch + "\n" - ) - modified.write(data) - - -def get_psi(): - psi = [] - for resource in ["cpu", "memory", "io"]: - with open("/proc/pressure/" + resource) as f: - psi.append(f.readline().split()[4].split("=")[1]) - return psi - - -def supports_psi(): - psi = [] - for resource in ["cpu", "memory", "io"]: - try: - with open("/proc/pressure/" + resource) as _: - psi.append(resource) - except Exception: - pass - if len(psi) == 3: - logging.info("PSI metrics supported") - return True - else: - logging.info("PSI metrics not supported") - return False - - -# fixed_tma_supported returns true if the fixed-purpose PMU counters for TMA events are supported on the target platform -def fixed_tma_supported(): - perf_out = "" - try: - perf = subprocess.Popen( - shlex.split( - "perf stat -a -e '{cpu/event=0x00,umask=0x04,period=10000003,name='TOPDOWN.SLOTS'/,cpu/event=0x00,umask=0x81,period=10000003,name='PERF_METRICS.BAD_SPECULATION'/}' sleep .1" - ), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - perf_out = perf.communicate()[0].decode() - except subprocess.CalledProcessError: - return False - - try: - events = { - a.split()[1]: int(a.split()[0].replace(",", "")) - for a in filter( - lambda x: "TOPDOWN.SLOTS" in x or "PERF_METRICS.BAD_SPECULATION" in x, - perf_out.split("\n"), - ) - } - except (IndexError, ValueError): - logging.debug("Failed to parse perf output in fixed_tma_supported()") - return False - try: - if events["TOPDOWN.SLOTS"] == events["PERF_METRICS.BAD_SPECULATION"]: - logging.debug("TOPDOWN.SLOTS and PERF_METRICS.BAD_SPECULATION are equal") - return False - except KeyError: - logging.debug("Failed to find required events in fixed_tma_supported()") - return False - - if events["TOPDOWN.SLOTS"] == 0 or events["PERF_METRICS.BAD_SPECULATION"] == 0: - logging.debug("TOPDOWN.SLOTS or PERF_METRICS.BAD_SPECULATION count is 0") - return False - - return True - - -# fixed_event_supported returns true if the fixed-purpose PMU counter for the given event (cpu-cycles or instructions) event is supported on the target platform -# it makes this determination by filling all the general purpose counters with the given events, then adding one more -def fixed_event_supported(arch, event): - num_gp_counters = 0 - if arch == "broadwell" or arch == "skylake" or arch == "cascadelake": - num_gp_counters = 4 - elif ( - arch == "icelake" - or arch == "sapphirerapids" - or arch == "emeraldrapids" - or arch == "sierraforest" - or arch == "graniterapids" - ): - num_gp_counters = 8 - else: - crash(f"Unsupported architecture: {arch}") - - perf_out = "" - events = ",".join([event] * (num_gp_counters + 1)) - try: - perf = subprocess.Popen( - shlex.split("perf stat -a -e '{" + events + "}' sleep .1"), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - perf_out = perf.communicate()[0].decode() - except subprocess.CalledProcessError: - return False - # on some VMs we see "" or "" in the perf output - if "" in perf_out or "" in perf_out: - return False - # on some VMs we get a count of 0 - for line in perf_out.split("\n"): - tokens = line.split() - if len(tokens) == 2 and tokens[0] == "0": - return False - return True - - -def fixed_cycles_supported(arch): - return fixed_event_supported(arch, "cpu-cycles") - - -def fixed_instructions_supported(arch): - return fixed_event_supported(arch, "instructions") - - -def ref_cycles_supported(): - perf_out = "" - try: - perf = subprocess.Popen( - shlex.split("perf stat -a -e ref-cycles sleep .1"), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - perf_out = perf.communicate()[0].decode() - except subprocess.CalledProcessError: - return False - - if "" in perf_out: - logging.warning( - "ref-cycles not enabled in VM driver. Contact system owner to enable. Collecting reduced metrics" - ) - return False - return True - - -def resource_path(relative_path): - """Get absolute path to resource, works for dev and for PyInstaller""" - base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) - return os.path.join(base_path, relative_path) - - -def validate_perfargs(perf): - """validate perf command before executing""" - if perf[0] != "perf": - crash("Not a perf command, exiting!") - - -def validate_file(fname): - """validate if file is accessible""" - if not os.access(fname, os.R_OK): - crash(str(fname) + " not accessible") - - -def get_eventfile_path(arch, script_path, supports_tma_fixed_events): - eventfile = None - if arch == "broadwell": - eventfile = "bdx.txt" - elif arch == "skylake" or arch == "cascadelake": - eventfile = "clx_skx.txt" - elif arch == "icelake": - if supports_tma_fixed_events: - eventfile = "icx.txt" - else: - eventfile = "icx_nofixedtma.txt" - elif arch == "sapphirerapids" or arch == "emeraldrapids": - if supports_tma_fixed_events: - eventfile = "spr_emr.txt" - else: - eventfile = "spr_emr_nofixedtma.txt" - elif arch == "sierraforest": - eventfile = "srf.txt" - elif arch == "graniterapids": - eventfile = "gnr.txt" - - if eventfile is None: - return None - - # Convert path of event file to relative path if being packaged by pyInstaller into a binary - if getattr(sys, "frozen", False): - basepath = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) - return os.path.join(basepath, eventfile) - elif __file__: - return script_path + "/events/" + eventfile - else: - crash("Unknown application type") - - -if __name__ == "__main__": - common.configure_logging(".") - if platform.system() != "Linux": - crash("PerfSpect currently supports Linux only") - - # fix the pyinstaller path - script_path = os.path.dirname(os.path.realpath(__file__)) - if "_MEI" in script_path: - script_path = script_path.rsplit("/", 1)[0] - default_output_file = "perfstat.csv" - - parser = ArgumentParser(description="perf-collect: Time series dump of PMUs") - duration = parser.add_mutually_exclusive_group() - runmode = parser.add_mutually_exclusive_group() - duration.add_argument( - "-t", "--timeout", type=int, default=None, help="perf event collection time" - ) - duration.add_argument( - "-a", - "--app", - type=str, - default=None, - help="Application to run with perf-collect, perf collection ends after workload completion", - ) - runmode.add_argument( - "-p", "--pid", type=str, default=None, help="perf-collect on selected PID(s)" - ) - runmode.add_argument( - "--cid", - help="perf-collect on up to 5 cgroups. Provide comma separated cids like e19f4fb59,6edca29db (by default, selects the 5 containers using the most CPU)", - type=str, - nargs="?", - const="", - ) - runmode.add_argument( - "-c", "--cpu", help="Collect for cpu metrics", action="store_true" - ) - runmode.add_argument( - "-s", "--socket", help="Collect for socket metrics", action="store_true" - ) - parser.add_argument( - "-m", - "--muxinterval", - type=int, - default=125, - help="event mux interval in milli seconds, default=125. Lower numbers can cause higher overhead", - ) - parser.add_argument( - "-o", - "--outcsv", - type=str, - default=default_output_file, - help="perf stat output in csv format, default=perfstat.csv", - ) - parser.add_argument( - "-v", "--verbose", help="Display debugging information", action="store_true" - ) - parser.add_argument( - "-V", "--version", help="display version info", action="store_true" - ) - parser.add_argument( - "-e", "--eventfile", default=None, help="Relative path to eventfile" - ) - args = parser.parse_args() - - if args.version: - print(perf_helpers.get_tool_version()) - sys.exit() - - is_root = os.geteuid() == 0 - if not is_root: - logging.warning( - "User is not root. See README.md for requirements and instructions on how to run as non-root user." - ) - try: - input("Press Enter to continue as non-root user or Ctrl-c to exit...") - except KeyboardInterrupt: - print("\nExiting...") - sys.exit() - - if not is_root: - # check kernel.perf_event_paranoid. It needs to be zero for non-root users. - paranoid = perf_helpers.check_perf_event_paranoid() - if paranoid is None: - crash("kernel.perf_event_paranoid could not be determined") - if paranoid != 0: - crash( - "kernel.perf_event_paranoid is set to " - + str(paranoid) - + ". Run as root or set it to 0" - ) - - # disable nmi watchdog before collecting perf - nmi_watchdog_status = perf_helpers.nmi_watchdog_enabled() - if nmi_watchdog_status is None: - crash("NMI watchdog status could not be determined") - - if is_root and nmi_watchdog_status: - perf_helpers.disable_nmi_watchdog() - - interval = 5000 - collect_psi = False - - if args.cpu: - logging.info("Run mode: cpu") - collect_psi = supports_psi() - elif args.socket: - logging.info("Run mode: socket") - collect_psi = supports_psi() - elif args.pid is not None: - logging.info("Run mode: pid") - elif args.cid is not None: - logging.info("Run mode: cid") - else: - logging.info("Run mode: system") - collect_psi = supports_psi() - - if args.muxinterval > 1000: - crash("Input argument muxinterval is too large, max is [1s or 1000ms]") - - # check if pmu available - if "cpu-cycles" not in perf_helpers.get_perf_list(): - crash( - "PMU's not available. Run baremetal or in a VM which exposes PMUs (sometimes full socket)" - ) - - procinfo = perf_helpers.get_cpuinfo() - arch, cpuname = perf_helpers.get_arch_and_name(procinfo) - if not arch: - crash( - f"Unrecognized CPU architecture. Supported architectures: {', '.join(SUPPORTED_ARCHITECTURES)}" - ) - - # Can we use the fixed purpose PMU counters for TMA events? - # The fixed-purpose PMU counters for TMA events are not supported on architectures older than Icelake - # They are also not supported on some VMs, e.g., AWS ICX and SPR VMs - supports_tma_fixed_events = False - if ( - arch == "icelake" - or arch == "sapphirerapids" - or arch == "emeraldrapids" - or arch == "graniterapids" - ): - supports_tma_fixed_events = fixed_tma_supported() - if not supports_tma_fixed_events: - logging.warning( - "Due to lack of vPMU support, some TMA events will not be collected" - ) - - # Can we use the fixed-purpose PMU counter for the cpu-cycles event? - supports_cycles_fixed_event = fixed_cycles_supported(arch) - - # Can we use the fixed-purpose PMU counter for the instructions event? - supports_instructions_fixed_event = fixed_instructions_supported(arch) - - # select architecture default event file if not supplied - if args.eventfile is not None: - eventfile = args.eventfile - else: - eventfile = get_eventfile_path(arch, script_path, supports_tma_fixed_events) - if eventfile is None: - crash(f"failed to match architecture ({arch}) to event file name.") - - logging.info("Event file: " + eventfile) - - supports_uncore_events = True - sys_devs = perf_helpers.get_sys_devices() - if ( - "uncore_cha" not in sys_devs - and "uncore_cbox" not in sys_devs - and "uncore_upi" not in sys_devs - and "uncore_qpi" not in sys_devs - and "uncore_imc" not in sys_devs - ): - logging.info("uncore devices not found (possibly in a vm?)") - supports_uncore_events = False - - supports_ref_cycles_event = ref_cycles_supported() - - events, collection_events = prep_events.prepare_perf_events( - eventfile, - (args.pid is not None or args.cid is not None or not supports_uncore_events), - supports_tma_fixed_events, - supports_uncore_events, - supports_ref_cycles_event, - ) - - # check output file is writable - if not perf_helpers.check_file_writeable(args.outcsv): - crash("Output file %s not writeable " % args.outcsv) - - # adjust mux interval - mux_intervals = perf_helpers.get_perf_event_mux_interval() - if args.muxinterval > 0: - if is_root: - logging.info( - "changing perf mux interval to " + str(args.muxinterval) + "ms" - ) - perf_helpers.set_perf_event_mux_interval( - False, args.muxinterval, mux_intervals - ) - else: - for device, mux in mux_intervals.items(): - mux_int = -1 - try: - mux_int = int(mux) - except ValueError: - crash("Failed to parse mux interval on " + device) - if mux_int != args.muxinterval: - crash( - "mux interval on " - + device - + " is set to " - + str(mux_int) - + ". Run as root or set it to " - + str(args.muxinterval) - + "." - ) - - # parse cgroups - cgroups = [] - if args.cid is not None: - cgroups = perf_helpers.get_cgroups(args.cid) - - if args.pid is not None or args.cid is not None: - logging.info("Not collecting uncore events in this run mode") - - pmu_driver_version = perf_helpers.get_pmu_driver_version() - - # log some metadata - logging.info("Architecture: " + arch) - logging.info("Model: " + cpuname) - logging.info("Kernel version: " + perf_helpers.get_version()) - logging.info("PMU driver version: " + pmu_driver_version) - logging.info("Uncore events supported: " + str(supports_uncore_events)) - logging.info( - "Fixed counter TMA events supported: " + str(supports_tma_fixed_events) - ) - logging.info( - "Fixed counter cpu-cycles event supported: " + str(supports_cycles_fixed_event) - ) - logging.info( - "Fixed counter instructions event supported: " - + str(supports_instructions_fixed_event) - ) - logging.info("ref-cycles event supported: " + str(supports_ref_cycles_event)) - logging.info("Cores per socket: " + str(perf_helpers.get_cpu_count())) - logging.info("Socket: " + str(perf_helpers.get_socket_count())) - logging.info("Hyperthreading on: " + str(perf_helpers.get_ht_status())) - counts = perf_helpers.get_unc_device_counts() - logging.info("IMC count: " + str(counts["imc"])) - logging.info("CHA per socket: " + str(counts["cha"])) - logging.info("UPI count: " + str(counts["upi"])) - logging.info("B2CMI count: " + str(counts["b2cmi"])) - logging.info("PerfSpect version: " + perf_helpers.get_tool_version()) - if args.verbose: - logging.info("/sys/devices/: " + str(sys_devs)) - - # build perf stat command - collection_type = "-a" if not args.cpu and not args.socket else "-a -A" - cmd = f"perf stat -I {interval} -x , {collection_type} -o {args.outcsv}" - if args.pid: - cmd += f" --pid {args.pid}" - - if args.cid is not None: - perf_format = prep_events.get_cgroup_events_format( - cgroups, events, len(collection_events) - ) - cmd += f" {perf_format}" - else: - cmd += f" -e {events}" - - if args.timeout: - cmd += f" sleep {args.timeout}" - elif args.app: - cmd += f" {args.app}" - - if args.verbose: - logging.info(cmd) - perfargs = shlex.split(cmd) - validate_perfargs(perfargs) - - psi = [] - logging.info("Collection started!") - start = time.time() - try: - perf = subprocess.Popen(perfargs) # nosec - try: - while perf.poll() is None: - if collect_psi: - psi.append(get_psi()) - time.sleep(interval / 1000) - except KeyboardInterrupt: - perf.kill() - except KeyboardInterrupt: - logging.info("Perfspect was interrupted by the user.") - except subprocess.SubprocessError as e: - crash("Failed to start perf\n" + str(e)) - end = time.time() - if end - start < 7: - logging.warning( - "PerfSpect was run for a short duration, some events might be zero or blank because they never got scheduled" - ) - logging.info("Collection complete!") - - cpuid_info = perf_helpers.get_cpuid_info(procinfo) - if collect_psi: - psi.append(get_psi()) - write_metadata( - args.outcsv, - collection_events, - arch, - cpuname, - cpuid_info, - pmu_driver_version, - supports_tma_fixed_events, - args.muxinterval, - args.cpu, - args.socket, - list(map(list, zip(*psi))), - ) - - os.chmod(args.outcsv, 0o666) # nosec - - # reset nmi_watchdog to what it was before running perfspect - if is_root and nmi_watchdog_status is True: - perf_helpers.enable_nmi_watchdog() - - if is_root: - logging.info("changing perf mux interval back to default") - perf_helpers.set_perf_event_mux_interval(True, 1, mux_intervals) - - logging.info("perf stat dumped to %s" % args.outcsv) diff --git a/perf-collect.spec b/perf-collect.spec deleted file mode 100644 index ce697e9..0000000 --- a/perf-collect.spec +++ /dev/null @@ -1,48 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - - -block_cipher = None - - -a = Analysis( - ['perf-collect.py'], - pathex=[], - datas=[('./src/libtsc.so', '.'), ('./events/bdx.txt', '.'), ('./events/clx_skx.txt', '.'), ('./events/icx.txt', '.'), ('./events/icx_nofixedtma.txt', '.'), ('./events/spr_emr.txt', '.'), ('./events/spr_emr_nofixedtma.txt', '.'), ('./events/srf.txt', '.'), ('./events/gnr.txt', '.')], - hiddenimports=[], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=['readline'], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False, -) - -# exclude libtinfo shared library from distributed binaries due to warning observed on Ubuntu 16.04: -# "/bin/bash: ./_MEIuU3XMv/libtinfo.so.5: no version information available (required by /bin/bash)" -a.binaries = [bin for bin in a.binaries if not bin[0].startswith('libtinfo')] - -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='perf-collect', - debug=False, - bootloader_ignore_signals=True, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir='.', - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, -) diff --git a/perf-postprocess.py b/perf-postprocess.py deleted file mode 100644 index 7657586..0000000 --- a/perf-postprocess.py +++ /dev/null @@ -1,1291 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -import json -import numpy as np -import logging -import os -import pandas as pd -import re -import sys -from argparse import ArgumentParser -from enum import Enum -from simpleeval import simple_eval -from src.common import crash -from src import common -from src import perf_helpers - - -class Mode(Enum): - System = 1 - Socket = 2 - CPU = 3 - - -# get the filenames for miscellaneous outputs -def get_extra_out_file(out_file, t): - dirname = os.path.dirname(out_file) - filename = os.path.basename(out_file) - t_file = "" - if t == "a": - text = "sys.average" - elif t == "r": - text = "sys.raw" - elif t == "s": - text = "socket" - elif t == "sa": - text = "socket.average" - elif t == "savg": - text = "socket.avg.pivot" - elif t == "smax": - text = "socket.max.pivot" - elif t == "smin": - text = "socket.min.pivot" - elif t == "sp95": - text = "socket.p95.pivot" - elif t == "sr": - text = "socket.raw" - elif t == "c": - text = "cpu" - elif t == "ca": - text = "cpu.average" - elif t == "cavg": - text = "cpu.avg.pivot" - elif t == "cmax": - text = "cpu.max.pivot" - elif t == "cmin": - text = "cpu.min.pivot" - elif t == "cp95": - text = "cpu.p95.pivot" - elif t == "cr": - text = "cpu.raw" - elif t == "m": - text = "sys" - - parts = os.path.splitext(filename) - if len(parts) == 1: - t_file = text + "." + filename - else: - t_file = parts[-2] + "." + text + ".csv" - return os.path.join(dirname, t_file) - - -def get_args(script_path): - parser = ArgumentParser(description="perf-postprocess: perf post process") - required_arg = parser.add_argument_group("required arguments") - required_arg.add_argument( - "-r", - "--rawfile", - type=str, - default="perfstat.csv", - help="Raw CSV output from perf-collect, default=perfstat.csv", - ) - parser.add_argument( - "--version", "-V", help="display version information", action="store_true" - ) - parser.add_argument( - "-o", - "--outfile", - type=str, - default=script_path + "/metric_out.csv", - help="perf stat outputs in csv format, default=metric_out.csv", - ) - parser.add_argument( - "-v", - "--verbose", - help="include debugging information, keeps all intermediate csv files", - action="store_true", - ) - parser.add_argument( - "-f", - "--fail-postprocessing", - help="gives exit code 1 when postprocessing detects missing event or zero division errors", - action="store_true", - ) - parser.add_argument( - "--rawevents", help="save raw events in .csv format", action="store_true" - ) - parser.add_argument( - "--pertxn", - type=int, - help="Generate per-transaction metrics using the provided transactions/sec.", - ) - parser.add_argument( - "-m", - "--metricfile", - default=None, - help="Relative path to metrics file in json format", - dest="metric_file", - ) - - args = parser.parse_args() - - # if args.version, print version then exit - if args.version: - print(perf_helpers.get_tool_version()) - sys.exit() - - # check number of transactions > 1 - if args.pertxn is not None: - if args.pertxn < 1: - crash("Number of transactions cannot be < 1" % args.outfile) - else: - args.outfile = args.outfile.replace(".csv", "_txn.csv") - - # check rawfile argument is given - if args.rawfile is None: - crash("Missing raw file, please provide raw csv generated using perf-collect") - - # check rawfile argument exists - if args.rawfile and not os.path.isfile(args.rawfile): - crash("perf raw data file not found, please provide valid raw file") - - # check output file is writable - if not perf_helpers.check_file_writeable(args.outfile): - crash("Output file %s not writeable " % args.outfile) - - return args - - -# fix c6-residency data lines -# for system: multiply value by number of HyperThreads -# for socket or cpu: add rows for each 2nd HyperThread with same values as 1st CPU -def get_fixed_c6_residency_fields(perf_data_lines, perf_mode): - # handle special case events: c6-residency - # if hyperthreading is disabled, no fixing is required - if meta_data["constants"]["HYPERTHREADING_ON"] == 0: - return perf_data_lines - - new_perf_data_lines = [] - if meta_data["constants"]["CONST_THREAD_COUNT"] == 2: - for fields in perf_data_lines: - if perf_mode == Mode.System and fields[3] == "cstate_core/c6-residency/": - # since "cstate_core/c6-residency/" is collected for only one cpu per core - # we double the value for the system wide collection (assign same value to the 2nd cpu) - try: - fields[1] = int(fields[1]) * 2 # fields[1] -> event value - except ValueError: - # value can be or - logging.warning( - "Failed to convert cstate_core/c6-residency/ metric value: " - + str(fields[1]) - + " to integer. Skipping" - ) - pass - new_perf_data_lines.append(fields) - elif fields[4] == "cstate_core/c6-residency/": - new_fields = fields.copy() - cpuID = int(fields[1].replace("CPU", "")) - HT_cpuID = cpuID + int( - meta_data["constants"]["CONST_THREAD_COUNT"] - * meta_data["constants"]["CORES_PER_SOCKET"] - ) - new_fields[1] = "CPU" + str(HT_cpuID) - new_perf_data_lines.append(fields) - new_perf_data_lines.append(new_fields) - else: - new_perf_data_lines.append(fields) - return new_perf_data_lines - - -# get metadata lines and perf events' lines in three separate lists -def get_all_data_lines(input_file_path): - with open(input_file_path, "r") as infile: - lines = infile.readlines() - - # input file has three headers: - # 1- ### META DATA ###, - # 2- ### PERF EVENTS ###, - # 3- ### PERF DATA ###, - - meta_data_lines = [] - perf_events_lines = [] - perf_data_lines = [] - - meta_data_started = False - perf_events_started = False - perf_data_started = False - for idx, line in enumerate(lines): - if line.strip() == "": # skip empty lines - continue - - # check first line is META Data header - elif (idx == 0) and ("### META DATA ###" not in line): - crash( - "The perf raw file doesn't contain metadata, please re-collect perf raw data" - ) - elif "### META DATA ###" in line: - meta_data_started = True - perf_events_started = False - perf_data_started = False - - elif "### PERF EVENTS ###" in line: - meta_data_started = False - perf_events_started = True - perf_data_started = False - - elif "### PERF DATA ###" in line: - meta_data_started = False - perf_events_started = False - perf_data_started = True - - elif meta_data_started: - meta_data_lines.append(line.strip()) - - elif perf_events_started: - perf_events_lines.append(line.strip()) - - elif perf_data_started: - if line.startswith("# started on"): - # this line is special, it is under "PERF DATA" (printed by perf), but it is treatesd as metadata - meta_data_lines.append(line.strip()) - else: - fields = line.split(",") - perf_data_lines.append(fields) - - if len(perf_data_lines) == 0: - crash( - "perfstat.csv contains no perf event data, try collecting for a longer time" - ) - return meta_data_lines, perf_events_lines, perf_data_lines - - -# return the number of cpus in a set -# example sets: -# simple range: "0-95" -# individual cpus: "1+4+5+9" -# individual cpus and range(s): "1+4-7+9+12-14" -def count_cpus_in_set(cpu_set): - count = 0 - subsets = cpu_set.split("+") - for subset in subsets: - if "-" in subset: - low_high = subset.split("-") - count += int(low_high[1]) - int(low_high[0]) + 1 - else: - count += 1 - return count - - -# get_metadata -def get_metadata_as_dict(meta_data_lines, txns=None): - meta_data = {} - meta_data["constants"] = {} - meta_data["metadata"] = {} - if txns is not None: - meta_data["constants"]["TXN"] = txns - - for line in meta_data_lines: - if line.startswith("SYSTEM_TSC_FREQ"): - meta_data["constants"]["SYSTEM_TSC_FREQ"] = ( - float(line.split(",")[1]) * 1000000 - ) - elif line.startswith("CORES_PER_SOCKET"): - meta_data["constants"]["CORES_PER_SOCKET"] = int(line.split(",")[1]) - elif line.startswith("HYPERTHREADING_ON"): - meta_data["constants"]["HYPERTHREADING_ON"] = int( - line.split(",")[1] == "True" - ) - meta_data["constants"]["CONST_THREAD_COUNT"] = ( - int(line.split(",")[1] == "True") + 1 - ) - elif line.startswith("SOCKET_COUNT"): - meta_data["constants"]["SOCKET_COUNT"] = int(line.split(",")[1]) - elif line.startswith("CHAS_PER_SOCKET") or line.startswith("CBOX"): - meta_data["constants"]["CHAS_PER_SOCKET"] = int(line.split(",")[1]) - elif line.startswith("Architecture"): - meta_data["constants"]["CONST_ARCH"] = str(line.split(",")[1]) - - elif line.startswith("Event grouping"): - meta_data["EVENT_GROUPING"] = ( - True if (str(line.split(",")[1]) == "enabled") else False - ) - elif line.startswith("cgroups"): - if line.startswith("cgroups=disabled"): - meta_data["CGROUPS"] = "disabled" - continue - # Get cgroup status and cgroup_id to container_name mapping - meta_data["CGROUPS"] = "enabled" - meta_data["CGROUP_HASH"] = dict( - item.split("=") - for item in line.split("cgroups=enabled,")[1].rstrip(",\n").split(",") - ) - docker_HASH = [] - docker_HASH = list(meta_data["CGROUP_HASH"].values()) - elif ( - line.startswith("cpusets") - and "CGROUPS" in meta_data - and meta_data["CGROUPS"] == "enabled" - ): - line = line.replace("cpusets,", "") - docker_SETS = [] - docker_SETS = line.split(",") - docker_SETS = docker_SETS[:-1] - # here length of docker_HASH should be exactly len(docker_SETS) - assert len(docker_HASH) == len(docker_SETS) - meta_data["CPUSETS"] = {} - for i, docker_SET in enumerate(docker_SETS): - num_of_cpus = count_cpus_in_set(docker_SET) - meta_data["CPUSETS"][docker_HASH[i]] = num_of_cpus - - elif line.startswith("Percpu mode"): - meta_data["PERCPU_MODE"] = ( - True if (str(line.split(",")[1]) == "enabled") else False - ) - - elif line.startswith("Persocket mode"): - meta_data["PERSOCKET_MODE"] = ( - True if (str(line.split(",")[1]) == "enabled") else False - ) - - elif line.startswith("# started on"): - meta_data["TIME_ZONE"] = str(line.split("# started on")[1]) - - elif line.startswith("Socket"): - if "SOCKET_CORES" not in meta_data: - meta_data["SOCKET_CORES"] = [] - CPUs = ((line.split("\n")[0]).split(",")[1]).split(";")[:-1] - meta_data["SOCKET_CORES"].append(CPUs) - elif line.startswith("PSI"): - meta_data["PSI"] = json.loads(line.split("PSI,")[1]) - - for line in meta_data_lines: - for info in [ - "SYSTEM_TSC_FREQ (MHz)", - "CORES_PER_SOCKET", - "SOCKET_COUNT", - "HYPERTHREADING_ON", - "IMC count", - "CHAS_PER_SOCKET", - "UPI count", - "Architecture", - "Model", - "kernel version", - "PerfSpect version", - "PMUDriverVersion", - "FixedTMASupported", - ]: - if info in line: - meta_data["metadata"][info] = line.split(",", 1)[1] - if meta_data["metadata"][info][-1] == ",": - meta_data["metadata"][info] = meta_data["metadata"][info][:-1] - - return meta_data - - -def set_CONST_TSC(meta_data, perf_mode, num_cpus=0): - if perf_mode == Mode.System: - if meta_data["CGROUPS"] == "enabled" and num_cpus > 0: - meta_data["constants"]["TSC"] = ( - meta_data["constants"]["SYSTEM_TSC_FREQ"] * num_cpus - ) - else: - meta_data["constants"]["TSC"] = ( - meta_data["constants"]["SYSTEM_TSC_FREQ"] - * meta_data["constants"]["CORES_PER_SOCKET"] - * meta_data["constants"]["CONST_THREAD_COUNT"] - * meta_data["constants"]["SOCKET_COUNT"] - ) - elif perf_mode == Mode.Socket: - meta_data["constants"]["TSC"] = ( - meta_data["constants"]["SYSTEM_TSC_FREQ"] - * meta_data["constants"]["CORES_PER_SOCKET"] - * meta_data["constants"]["CONST_THREAD_COUNT"] - ) - elif perf_mode == Mode.CPU: - meta_data["constants"]["TSC"] = meta_data["constants"]["SYSTEM_TSC_FREQ"] - return - - -def get_event_name(event_line): - event_name = event_line - if "name=" in event_name: - matches = re.findall(r"\.*name=\'(.*?)\'.*", event_name) - assert len(matches) > 0 - event_name = matches[0] - if event_name.endswith(":c"): # core event - event_name = event_name.split(":c")[0] - if event_name.endswith(":u"): # uncore event - event_name = event_name.split(":u")[0] - # clean up , or ; - event_name = event_name.replace(",", "").replace(";", "") - - return event_name - - -def get_event_groups(event_lines): - groups = {} - group_indx = 0 - - current_group = [] - for event in event_lines: - if ";" in event: # end of group - current_group.append(get_event_name(event)) - groups["group_" + str(group_indx)] = current_group - group_indx += 1 - current_group = [] - else: - current_group.append(get_event_name(event)) - return groups - - -def get_metric_file_name(microarchitecture, fixed_tma_supported): - metric_file = "" - if microarchitecture == "broadwell": - metric_file = "metric_bdx.json" - elif microarchitecture == "skylake" or microarchitecture == "cascadelake": - metric_file = "metric_skx_clx.json" - elif microarchitecture == "icelake": - if fixed_tma_supported: - metric_file = "metric_icx.json" - else: - metric_file = "metric_icx_nofixedtma.json" - elif microarchitecture == "sapphirerapids" or microarchitecture == "emeraldrapids": - if fixed_tma_supported: - metric_file = "metric_spr_emr.json" - else: - metric_file = "metric_spr_emr_nofixedtma.json" - elif microarchitecture == "sierraforest": - metric_file = "metric_srf.json" - elif microarchitecture == "graniterapids": - metric_file = "metric_gnr.json" - else: - crash("Suitable metric file not found") - - # Convert path of json file to relative path if being packaged by pyInstaller into a binary - if getattr(sys, "frozen", False): - basepath = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) - metric_file = os.path.join(basepath, metric_file) - elif __file__: - metric_file = script_path + "/events/" + metric_file - else: - crash("Unknown application type") - return metric_file - - -def validate_file(fname): - if not os.access(fname, os.R_OK): - crash(str(fname) + " not accessible") - - -def get_metrics_formula(architecture, fixed_tma_supported, metric_file=None, txns=None): - # get the metric file name based on architecture - if metric_file is None: - metric_file = get_metric_file_name(architecture, fixed_tma_supported) - logging.info("Metric file: " + metric_file) - validate_file(metric_file) - - with open(metric_file, "r") as f_metric: - try: - metrics = json.load(f_metric) - for metric in metrics: - if txns is not None: - if "name-txn" in metric: - metric["name"] = metric["name-txn"] - if "expression-txn" in metric: - metric["expression"] = metric["expression-txn"] - metric["events"] = re.findall(r"\[(.*?)\]", metric["expression"]) - return metrics - except json.decoder.JSONDecodeError: - crash("Invalid JSON, please provide a valid JSON as metrics file") - return - - -def get_socket_number(sockets_dict, CPU): - CPU_index = CPU.replace("CPU", "") - for s in range(len(sockets_dict)): - if CPU_index in sockets_dict[s]: - return s - return - - -def extract_dataframe(perf_data_lines, meta_data, perf_mode): - logging.info("Parsing event data") - # parse event data into dataframe and set header names - perf_data_df = pd.DataFrame(perf_data_lines) - if "CGROUPS" in meta_data and meta_data["CGROUPS"] == "enabled": - # 1.001044566,6261968509,,L1D.REPLACEMENT,/system.slice/docker-826c1c9de0bde13b0c3de7c4d96b38710cfb67c2911f30622508905ece7e0a16.scope,6789274819,5.39,, - assert len(perf_data_df.columns) >= 7 - columns = [ - "ts", - "value", - "col0", - "metric", - "cgroup", - "perf_group_id", - "percentage", - ] - # add dummy col names for remaining columns - for col in range(7, len(perf_data_df.columns)): - columns.append("col" + str(col)) - perf_data_df.columns = columns - elif perf_mode == Mode.System: - # Ubuntu 16.04 returns 6 columns, later Ubuntu's and other OS's return 8 columns - assert len(perf_data_df.columns) >= 6 - columns = ["ts", "value", "col0", "metric", "perf_group_id", "percentage"] - # add dummy col names for remaining columns - for col in range(6, len(perf_data_df.columns)): - columns.append("col" + str(col)) - perf_data_df.columns = columns - elif perf_mode == Mode.CPU or perf_mode == Mode.Socket: - assert len(perf_data_df.columns) >= 7 - columns = [ - "ts", - "cpu", - "value", - "col0", - "metric", - "perf_group_id", - "percentage", - ] - # add dummy col names for remaining columns - for col in range(7, len(perf_data_df.columns)): - columns.append("col" + str(col)) - perf_data_df.columns = columns - # Add socket column - perf_data_df["socket"] = perf_data_df.apply( - lambda x: "S" + str(get_socket_number(meta_data["SOCKET_CORES"], x["cpu"])), - axis=1, - ) - - # fix metric name X.1, X.2, etc -> just X - perf_data_df["metric"] = perf_data_df.apply( - lambda x: ( - ".".join(x["metric"].split(".")[:-1]) - if len(re.findall(r"^[0-9]*$", x["metric"].split(".")[-1])) > 0 - else x["metric"] - ), - axis=1, - ) - - # set data frame types - perf_data_df["value"] = pd.to_numeric( - perf_data_df["value"], errors="coerce" - ).fillna(0) - - return perf_data_df - - -# get group data frame after grouping -def get_group_df_from_full_frame( - time_slice_df, start_index, end_of_group_index, perf_mode -): - g_df = time_slice_df[start_index:end_of_group_index] - if perf_mode == Mode.System: - g_df = g_df[["metric", "value"]].groupby("metric")["value"].sum().to_frame() - elif perf_mode == Mode.Socket: - if "socket" in g_df: - g_df = ( - g_df[["metric", "socket", "value"]] - .groupby(["metric", "socket"])["value"] - .sum() - .to_frame() - ) - else: - crash("No socket information found, exiting...") - elif perf_mode == Mode.CPU: # check dataframe has cpu column, otherwise raise error - if "cpu" in g_df: - g_df = ( - g_df[["metric", "cpu", "value"]] - .groupby(["metric", "cpu"])["value"] - .sum() - .to_frame() - ) - else: - crash("No CPU information found, exiting...") - - return g_df - - -def generate_metrics_time_series(time_series_df, perf_mode, out_file_path): - time_series_df_T = time_series_df.T - time_series_df_T.index.name = "time" - metric_file_name = "" - if perf_mode == Mode.System: - metric_file_name = get_extra_out_file(out_file_path, "m") - if perf_mode == Mode.Socket: - metric_file_name = get_extra_out_file(out_file_path, "s") - - if perf_mode == Mode.CPU: - metric_file_name = get_extra_out_file(out_file_path, "c") - # generate metrics with time indexes - time_series_df_T.to_csv(metric_file_name) - return - - -def generate_metrics_averages( - time_series_df: pd.DataFrame, perf_mode: Mode, out_file_path: str, metrics -) -> None: - time_series_df.index.name = "metrics" - avgcol = time_series_df.mean(numeric_only=True, axis=1).to_frame().reset_index() - p95col = time_series_df.quantile(q=0.95, axis=1).to_frame().reset_index() - mincol = time_series_df.min(axis=1).to_frame().reset_index() - maxcol = time_series_df.max(axis=1).to_frame().reset_index() - # define columns headers - avgcol.columns = ["metrics", "avg"] - p95col.columns = ["metrics", "p95"] - mincol.columns = ["metrics", "min"] - maxcol.columns = ["metrics", "max"] - - # merge columns - time_series_df = time_series_df.merge(avgcol, on="metrics", how="outer") - time_series_df = time_series_df.merge(p95col, on="metrics", how="outer") - time_series_df = time_series_df.merge(mincol, on="metrics", how="outer") - time_series_df = time_series_df.merge(maxcol, on="metrics", how="outer") - - average_metric_file_name = "" - if perf_mode == Mode.System: - average_metric_file_name = get_extra_out_file(out_file_path, "a") - elif perf_mode == Mode.CPU: - average_metric_file_name = get_extra_out_file(out_file_path, "ca") - elif perf_mode == Mode.Socket: - average_metric_file_name = get_extra_out_file(out_file_path, "sa") - - time_series_df[["metrics", "avg", "p95", "min", "max"]].to_csv( - average_metric_file_name, index=False - ) - if perf_mode != Mode.System: - for table, type in [ - [avgcol, "avg"], - [p95col, "p95"], - [mincol, "min"], - [maxcol, "max"], - ]: - table["part"] = table["metrics"].map( - lambda x: int("".join(filter(str.isdigit, x.split(".")[-1]))) - ) - table["metrics"] = table["metrics"].map(lambda x: x.rsplit(".", 1)[0]) - table = table.pivot_table( - index=["metrics"], columns=["part"], values=table.columns[1] - ) - table = table.reindex(index=metrics) - table = table.reindex(sorted(table.columns), axis=1) - - average_metric_file_name = get_extra_out_file( - out_file_path, ("s" if perf_mode == Mode.Socket else "c") + type - ) - table.to_csv(average_metric_file_name) - return - - -def row(df, name): - if name in df.index: - timeseries = df.loc[[name]].to_dict("split") - timeseries["columns"] = map(lambda x: round(float(x), 1), timeseries["columns"]) - return json.dumps(list(zip(timeseries["columns"], timeseries["data"][0]))) - else: - return "[]" - - -def write_html(time_series_df, perf_mode, out_file_path, meta_data, pertxn=None): - html_file = "base.html" - if getattr(sys, "frozen", False): - basepath = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) - html_file = os.path.join(basepath, html_file) - elif __file__: - html_file = script_path + "/src/" + html_file - else: - crash("Unknown application type") - - html = "" - with open(html_file, "r", encoding="utf-8") as f_html: - html = f_html.read() - - # only show TMA if system-wide mode - if perf_mode == Mode.System: - html = html.replace("TRANSACTIONS", str(pertxn is not None).lower()) - time_series_df.index.name = "metrics" - for metric in [ - ["CPUUTIL", "metric_CPU utilization %"], - ["CPIDATA", "metric_CPI"], - ["CPUFREQ", "metric_CPU operating frequency (in GHz)"], - ["CPIDATA", "metric_CPI"], - ["PKGPOWER", "metric_package power (watts)"], - ["DRAMPOWER", "metric_DRAM power (watts)"], - ["L1DATA", "metric_L1D MPI (includes data+rfo w/ prefetches)"], - ["L2DATA", "metric_L2 MPI (includes code+data+rfo w/ prefetches)"], - ["LLCDATA", "metric_LLC data read MPI (demand+prefetch)"], - ["READDATA", "metric_memory bandwidth read (MB/sec)"], - ["WRITEDATA", "metric_memory bandwidth write (MB/sec)"], - ["TOTALDATA", "metric_memory bandwidth total (MB/sec)"], - ["REMOTENUMA", "metric_NUMA %_Reads addressed to remote DRAM"], - ]: - new_metric = metric[1] - if pertxn is not None: - if "_CPI" in new_metric: - new_metric = new_metric.replace("_CPI", "_cycles per txn") - if " MPI" in new_metric: - new_metric = new_metric.replace(" MPI", " misses per txn") - html = html.replace(metric[0], row(time_series_df, new_metric)) - - avg = time_series_df.mean(numeric_only=True, axis=1).to_frame() - html = html.replace( - "ALLMETRICS", json.dumps(avg.reset_index().to_dict("records")) - ) - html = html.replace("METADATA", json.dumps(list(meta_data["metadata"].items()))) - for number in [ - ["FRONTEND", "metric_TMA_Frontend_Bound(%)"], - ["BACKEND", "metric_TMA_Backend_Bound(%)"], - ["COREDATA", "metric_TMA_..Core_Bound(%)"], - ["MEMORY", "metric_TMA_..Memory_Bound(%)"], - ["BADSPECULATION", "metric_TMA_Bad_Speculation(%)"], - ["RETIRING", "metric_TMA_Retiring(%)"], - ["PSI_CPU", "cpu stall %"], - ["PSI_MEM", "memory stall %"], - ["PSI_IO", "io stall %"], - ]: - try: - html = html.replace(number[0], str(avg.loc[number[1], 0])) - except Exception: - html = html.replace(number[0], "0") - - with open( - os.path.splitext(out_file_path)[0] + ".html", "w", encoding="utf-8" - ) as file: - file.write(html) - - -def log_skip_metric(metric, instance, msg): - logging.warning( - msg - + ': metric "' - + metric["name"] - + '" expression "' - + metric["expression"] - + '" values "' - + instance - + '"' - ) - - -# group_start_end_index_dict is both an input and output argument -# if empty, the start and end indexes for each group will be added -# if not, the start and end indexes for each group will be read from it -def get_groups_to_dataframes( - time_slice_df, group_to_event, group_start_end_index_dict, perf_mode -): - group_to_df = {} - if len(group_start_end_index_dict) == 0: - current_group_indx = 0 - group_name = "group_" + str(current_group_indx) - event_list = group_to_event[group_name] - start_index = 0 - end_index = 0 - for i in time_slice_df.index: - row = time_slice_df.loc[i] - if row["metric"] in event_list: - end_index += 1 - else: - group_to_df[group_name] = get_group_df_from_full_frame( - time_slice_df, start_index, end_index, perf_mode - ) - group_start_end_index_dict[group_name] = (start_index, end_index) - start_index = end_index - current_group_indx += 1 - try: - group_name = "group_" + str(current_group_indx) - event_list = group_to_event[group_name] - except KeyError: - crash( - "could not find " - + str(row) - + " in event grouping: " - + str(group_to_event) - ) - end_index += 1 - group_to_df[group_name] = get_group_df_from_full_frame( - time_slice_df, start_index, time_slice_df.shape[0], perf_mode - ) - group_start_end_index_dict[group_name] = (start_index, time_slice_df.shape[0]) - else: - for group_name in group_start_end_index_dict: - start_index = group_start_end_index_dict[group_name][0] - end_index = group_start_end_index_dict[group_name][1] - group_to_df[group_name] = get_group_df_from_full_frame( - time_slice_df, start_index, end_index, perf_mode - ) - return group_to_df - - -def substitute_constants(expression, constants): - returned_expression = expression - for constant in constants: - returned_expression = returned_expression.replace( - "[" + constant + "]", str(constants[constant]) - ) - return returned_expression - - -# Find the best group to use to evalaute a set of events -# The best group is the one that has the majority of the events -# For example, to evaluate events [ev1, ev2, ev3, ev4] -# If group 1 has [ev1,ev2] and group 2 has [ev1, ev2, ev3] -# Then group 2 is better than group 1 -def find_best_group(remaining_events, group_to_event): - diff_size = sys.maxsize - best_group = None - for group, events in group_to_event.items(): - ds = len(set(remaining_events) - set(events)) - if ds < diff_size and ds < len(set(remaining_events)): - diff_size = ds - best_group = group - if diff_size == 0: - break - return best_group - - -# substitute the value of an event in the given expression -# "exp_to_evaluate" is modified by the function and added to "evaluated_expressions" -# detected errors are added to "errors" -# in arguments: verbose, best_group, group_to_df, event,exp_to_evaluate, -# out arguments: errors, evaluated_expressions, -def substitute_event_in_expression( - verbose, - best_group, - group_to_df, - event, - exp_to_evaluate, - errors, - evaluated_expressions, -): - if best_group in group_to_df: - g_df = group_to_df[best_group] - event_df = g_df.loc[event] - if event_df.shape == (1,): # system wide - if "sys" not in evaluated_expressions: - evaluated_expressions["sys"] = exp_to_evaluate.replace( - "[" + event + "]", str(event_df.iloc[0]) - ) - else: - evaluated_expressions["sys"] = evaluated_expressions["sys"].replace( - "[" + event + "]", str(event_df.iloc[0]) - ) - else: - for index in event_df.index: - value = event_df["value"][index] - if index not in evaluated_expressions: - evaluated_expressions[index] = exp_to_evaluate - evaluated_expressions[index] = evaluated_expressions[index].replace( - "[" + event + "]", - str(value), - ) - else: # group was not counted - if verbose and best_group not in errors["NOT COUNTED GROUPS"]: - errors["NOT COUNTED GROUPS"].add(best_group) - logging.warning("Event group:" + best_group + "Not counted") - return - - -# evaluate the expression of a given metric -# returns the metric name (and subname) and the evaluation result -# detected errors will be appended to "errors" -def evaluate_metric_expression( - expressions_to_evaluate, verbose, metric, instance, errors -): - if ( - "[" in expressions_to_evaluate[instance] - or "]" in expressions_to_evaluate[instance] - ): - if verbose and metric["name"] not in errors["MISSING DATA"]: - errors["MISSING DATA"].add(metric["name"]) - log_skip_metric(metric, expressions_to_evaluate[instance], "MISSING DATA") - return None - try: - result = "{:.8f}".format( - simple_eval( - expressions_to_evaluate[instance], - functions={"min": min, "max": max}, - ) - ) - except ZeroDivisionError: - if verbose and metric["name"] not in errors["ZERO DIVISION"]: - errors["ZERO DIVISION"].add(metric["name"]) - log_skip_metric(metric, expressions_to_evaluate[instance], "ZERO DIVISION") - result = 0 - sub_txt = "" if instance == "sys" else "." + str(instance) - return metric["name"] + sub_txt, float(result) - - -# evaluate all metrics from dataframes in group_to_df -# for each metric, we find the best group to use to evaluate the metric's expression from -def evaluate_metrics(verbose, metrics, metadata, group_to_event, group_to_df, errors): - metrics_results = {} - best_groups_for_events = {} - for metric in metrics: - non_constant_events = [] - exp_to_evaluate = substitute_constants( - metric["expression"], metadata["constants"] - ) - for event in metric["events"]: - if event.upper() in metadata["constants"]: - exp_to_evaluate = substitute_constants( - exp_to_evaluate, - {event.upper(): metadata["constants"][event.upper()]}, - ) - else: - non_constant_events.append(event) - - remaining_events_to_find = list(non_constant_events) - evaluated_expressions = {} - passes = 0 - - while len(remaining_events_to_find) > 0: - if ( - passes == 1 - and verbose - and metric["name"] not in errors["MULTIPLE GROUPS"] - ): - errors["MULTIPLE GROUPS"].add(metric["name"]) - logging.warning( - f'MULTIPLE GROUPS: metric "{metric["name"]}", events "{set(non_constant_events)}"' - ) - passes += 1 - remaining_events_txt = str(remaining_events_to_find) - if remaining_events_txt in best_groups_for_events: - best_group = best_groups_for_events[remaining_events_txt] - else: - best_group = find_best_group(remaining_events_to_find, group_to_event) - best_groups_for_events[remaining_events_txt] = best_group - - if best_group is None: - break - for event in remaining_events_to_find[:]: - if event in group_to_event[best_group]: - remaining_events_to_find.remove(event) - substitute_event_in_expression( - verbose, - best_group, - group_to_df, - event, - exp_to_evaluate, - errors, - evaluated_expressions, - ) - - if len(remaining_events_to_find) == 0: - for instance in evaluated_expressions: - metric_result = evaluate_metric_expression( - evaluated_expressions, verbose, metric, instance, errors - ) - if metric_result is not None: - metrics_results[metric_result[0]] = metric_result[1] - else: - if verbose and metric["name"] not in errors["MISSING EVENTS"]: - logging.warning( - 'MISSING EVENTS: metric "' - + metric["name"] - + '" events "' - + str(remaining_events_to_find) - + '"' - ) - errors["MISSING EVENTS"].add(metric["name"]) - continue - return metrics_results - - -def generate_metrics( - perf_data_df, - out_file_path, - group_to_event, - metadata, - metrics, - perf_mode, - pertxn=None, - verbose=False, - fail_postprocessing=False, -): - # filter out uncore metrics if in cpu or socket mode - filtered_metrics = [] - for m in metrics: - if perf_mode == Mode.CPU or perf_mode == Mode.Socket: - if any( - [ - e.startswith("power/") - or e.startswith("cstate_") - or e.startswith("UNC_") - for e in m["events"] - ] - ): - continue - filtered_metrics.append(m) - - time_slice_groups = perf_data_df.groupby("ts", sort=False) - time_metrics_result = {} - errors = { - "MISSING DATA": set(), - "ZERO DIVISION": set(), - "MISSING EVENTS": set(), - "MULTIPLE GROUPS": set(), - "NOT COUNTED GROUPS": set(), - } - prev_time_slice = 0 - logging.info( - "processing " - + str(time_slice_groups.ngroups) - + " samples in " - + ( - "System" - if perf_mode == Mode.System - else "CPU" if perf_mode == Mode.CPU else "Socket" - ) - + " mode" - ) - group_start_end_index_dict = {} - for time_slice, item in time_slice_groups: - time_slice_float = float(time_slice) - if time_slice_float - prev_time_slice < 4.5: - logging.warning("throwing out last sample because it was too short") - if time_slice_groups.ngroups == 1: - crash("no remaining samples") - continue - time_slice_df = time_slice_groups.get_group(time_slice).copy() - # normalize by difference between current time slice and previous time slice - # this ensures that all our events are per-second, even if perf is collecting - # over a longer time slice - time_slice_df["value"] = time_slice_df["value"] / ( - time_slice_float - prev_time_slice - ) - prev_time_slice = time_slice_float - # get dictionary with group_ids as keys and group dataframes as values - # We save the start and end indexes for each group in the first iteration and use it in the following iterations - # group_start_end_index_dict is an out argument in the first iteration, and an input argument for following iterations - group_to_df = get_groups_to_dataframes( - time_slice_df, group_to_event, group_start_end_index_dict, perf_mode - ) - time_metrics_result[time_slice] = evaluate_metrics( - verbose, filtered_metrics, metadata, group_to_event, group_to_df, errors - ) - - metrics = list(time_metrics_result[list(time_metrics_result.keys())[0]].keys()) - - time_series_df = pd.DataFrame(time_metrics_result).reindex(index=metrics) - - if verbose: - for error in errors: - logging.warning( - str(len(errors[error])) + " " + error + ": " + str(errors[error]) - ) - if fail_postprocessing and ( - len(errors["MISSING EVENTS"]) > 0 or len(errors["ZERO DIVISION"]) > 0 - ): - crash("Failing due to postprocessing errors") - - # add psi - if len(meta_data["PSI"]) > 0 and perf_mode == Mode.System: - psi_len = range(len(time_series_df.columns)) - time_series_df.loc["cpu stall %"] = [ - (int(meta_data["PSI"][0][x + 1]) - int(meta_data["PSI"][0][x])) / 50000 - for x in psi_len - ] - time_series_df.loc["memory stall %"] = [ - (int(meta_data["PSI"][1][x + 1]) - int(meta_data["PSI"][1][x])) / 50000 - for x in psi_len - ] - time_series_df.loc["io stall %"] = [ - (int(meta_data["PSI"][2][x + 1]) - int(meta_data["PSI"][2][x])) / 50000 - for x in psi_len - ] - - generate_metrics_time_series(time_series_df, perf_mode, out_file_path) - generate_metrics_averages( - time_series_df, - perf_mode, - out_file_path, - [*dict.fromkeys([e.rsplit(".", 1)[0] for e in metrics])], - ) - if perf_mode == Mode.System: - write_html(time_series_df, perf_mode, out_file_path, meta_data, pertxn) - return - - -def generate_raw_events_system(perf_data_df, out_file_path): - perf_data_df_system_raw = ( - perf_data_df[["metric", "value"]].groupby("metric")["value"].sum().to_frame() - ) - last_time_stamp = float(perf_data_df["ts"].tail(1).values[0]) - # average per second. Last time stamp = total collection duration in seconds - perf_data_df_system_raw["avg"] = np.where( - perf_data_df_system_raw["value"] > 0, - perf_data_df_system_raw["value"] / last_time_stamp, - 0, - ) - - sys_raw_file_name = get_extra_out_file(out_file_path, "r") - perf_data_df_system_raw["avg"].to_csv(sys_raw_file_name) - - return - - -def generate_raw_events_socket(perf_data_df, out_file_path): - # print raw values persocket - perf_data_df_scoket_raw = ( - perf_data_df[["metric", "socket", "value"]] - .groupby(["metric", "socket"])["value"] - .sum() - .to_frame() - ) - last_time_stamp = float(perf_data_df["ts"].tail(1).values[0]) - perf_data_df_scoket_raw["avg"] = np.where( - perf_data_df_scoket_raw["value"] > 0, - perf_data_df_scoket_raw["value"] / last_time_stamp, - 0, - ) - - metric_per_socket_frame = pd.pivot_table( - perf_data_df_scoket_raw, - index="metric", - columns="socket", - values="avg", - fill_value=0, - ) - - socket_raw_file_name = get_extra_out_file(out_file_path, "sr") - metric_per_socket_frame.to_csv(socket_raw_file_name) - - return - - -def generate_raw_events_cpu(perf_data_df, out_file_path): - # print raw values per CPU - perf_data_df_CPU_raw = ( - perf_data_df[["metric", "cpu", "value"]] - .groupby(["metric", "cpu"])["value"] - .sum() - .to_frame() - ) - last_time_stamp = float(perf_data_df["ts"].tail(1).values[0]) - perf_data_df_CPU_raw["avg"] = np.where( - perf_data_df_CPU_raw["value"] > 0, - perf_data_df_CPU_raw["value"] / last_time_stamp, - 0, - ) - - metric_per_CPU_frame = pd.pivot_table( - perf_data_df_CPU_raw, - index="metric", - columns="cpu", - values="avg", - fill_value=0, - ) - # drop uncore and power metrics - to_drop = [] - for metric in metric_per_CPU_frame.index: - if metric.startswith("UNC_") or metric.startswith("power/"): - to_drop.append(metric) - metric_per_CPU_frame.drop(to_drop, inplace=True) - - CPU_raw_file_name = get_extra_out_file(out_file_path, "cr") - metric_per_CPU_frame.to_csv(CPU_raw_file_name) - - return - - -def generate_raw_events(perf_data_df, out_file_path, perf_mode): - if perf_mode.System: - generate_raw_events_system(perf_data_df, out_file_path) - elif perf_mode.Socket: - generate_raw_events_socket(perf_data_df, out_file_path) - elif perf_mode.CPU: - generate_raw_events_cpu(perf_data_df, out_file_path) - - -if __name__ == "__main__": - common.configure_logging(".") - script_path = os.path.dirname(os.path.realpath(__file__)) - if "_MEI" in script_path: - script_path = script_path.rsplit("/", 1)[0] - # Parse arguments and check validity - args = get_args(script_path) - input_file_path = args.rawfile - out_file_path = args.outfile - # read all metadata, perf events, and perf data lines - # Note: this might not be feasible for very large files - meta_data_lines, perf_event_lines, perf_data_lines = get_all_data_lines( - input_file_path - ) - - # parse metadata and get mode (system, socket, or CPU) - meta_data = get_metadata_as_dict(meta_data_lines, args.pertxn) - perf_mode = Mode.System - if "PERSOCKET_MODE" in meta_data and meta_data["PERSOCKET_MODE"]: - perf_mode = Mode.Socket - elif "PERCPU_MODE" in meta_data and meta_data["PERCPU_MODE"]: - perf_mode = Mode.CPU - - # fix c6 residency values - perf_data_lines = get_fixed_c6_residency_fields(perf_data_lines, perf_mode) - - # set const TSC according to perf_mode - set_CONST_TSC(meta_data, perf_mode) - - # parse event groups - event_groups = get_event_groups(perf_event_lines) - # extract data frame - perf_data_df = extract_dataframe(perf_data_lines, meta_data, perf_mode) - - # parse metrics expressions - metrics = get_metrics_formula( - meta_data["constants"]["CONST_ARCH"], - meta_data["metadata"]["FixedTMASupported"] == "True", - args.metric_file, - args.pertxn, - ) - - if args.rawevents: # generate raw events for system, socket and CPU - generate_raw_events(perf_data_df, out_file_path, perf_mode) - - # generate metrics for each cgroup - if "CGROUPS" in meta_data and meta_data["CGROUPS"] == "enabled": - for cgroup_id in meta_data["CGROUP_HASH"]: - container_id = meta_data["CGROUP_HASH"][cgroup_id] - set_CONST_TSC(meta_data, perf_mode, meta_data["CPUSETS"][container_id]) - cgroup_id_perf_data_df = perf_data_df[perf_data_df["cgroup"] == cgroup_id] - cgroup_id_out_file_path = ( - out_file_path.rsplit(".csv", 1)[0] - + "_" - + meta_data["CGROUP_HASH"][cgroup_id] - + ".csv" - ) - generate_metrics( - cgroup_id_perf_data_df, - cgroup_id_out_file_path, - event_groups, - meta_data, - metrics, - perf_mode, - args.pertxn, - args.verbose, - args.fail_postprocessing, - ) - else: - generate_metrics( - perf_data_df, - out_file_path, - event_groups, - meta_data, - metrics, - perf_mode, - args.pertxn, - args.verbose, - args.fail_postprocessing, - ) - if perf_mode != Mode.System: # always generate metrics on system level - set_CONST_TSC(meta_data, Mode.System) - generate_metrics( - perf_data_df, - out_file_path, - event_groups, - meta_data, - metrics, - Mode.System, - args.pertxn, - args.verbose, - args.fail_postprocessing, - ) - - logging.info("Generated results file(s) in: " + out_file_path.rsplit("/", 1)[0]) - logging.info("Done!") diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1b4aa88..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -black==24.8.0 -flake8 -pytype -simpleeval -pandas -pyinstaller -pytest diff --git a/scripts/check_events.py b/scripts/check_events.py new file mode 100755 index 0000000..5a387cd --- /dev/null +++ b/scripts/check_events.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# check_events.py - checks if all events used in metrics are present in the events file +# +# Usage: check_events.py [perfmon_events.json] + +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import json + + +def get_event(line): + if line != "" and not line.startswith("#"): + if line.find("name=") >= 0: + x = line[line.find("name=") + 5 :] + x = x[x.find("'") + 1 :] + x = x[0 : x.find("'")] + if x.find(":") > 0: + x = x[0 : x.find(":")] + else: + x = line[0:-2] + else: + x = None + return x + + +# check_events.py perfspect_metrics.json perfspect_events.txt +def main(): + if len(sys.argv) < 3: + print( + "Usage: check_events.py [perfmon_events.json]", + file=sys.stderr, + ) + sys.exit(1) + metrics_file = sys.argv[1] + events_file = sys.argv[2] + + with open(metrics_file, "r") as f: + metrics = json.load(f) + metric_list = {} + used_events = {} # event: count + for m in metrics: + metric = m["name"] + formula = m["expression"] + m_events = [] + start_bracket = formula.find("[") + while start_bracket >= 0: + end_bracket = formula.find("]") + event = formula[start_bracket + 1 : end_bracket] + if event not in [ + "SYSTEM_TSC_FREQ", + "TSC", + "CHAS_PER_SOCKET", + "SOCKET_COUNT", + "CORES_PER_SOCKET", + "HYPERTHREADING_ON", + "CONST_THREAD_COUNT", + ]: + if event.find(":") > 0 and event[-1] != "k": + event = event[0 : event.find(":")] + if not event.startswith("const_"): + used_events[event] = used_events.get(event, 0) + 1 + m_events.append(event) + formula = formula[end_bracket + 1 :] + start_bracket = formula.find("[") + metric_list[metric] = m_events + + event_list = [] + with open(events_file, "r") as f: + for line in f: + event = get_event(line) + if event is not None and event != "" and event_list.count(event) == 0: + event_list.append(event) + + taken_alone = [] # events that cannot be in the same group + all_taken_alone = [] + if len(sys.argv) > 3: + perfmon_events_file = sys.argv[3] + with open(perfmon_events_file, "r") as f: + perfmon_events = json.load(f) + for pm_event in perfmon_events["Events"]: + if pm_event["TakenAlone"] == 1: + all_taken_alone.append(pm_event["EventName"]) + if pm_event["TakenAlone"] == 1 and pm_event["EventName"] in event_list: + taken_alone.append(pm_event["EventName"]) + + missing_events = [x for x in used_events.keys() if not x in event_list] + unused_events = [x for x in event_list if not x in used_events.keys()] + missing_events_str = "\n".join(missing_events) + unused_events_str = "\n".join(unused_events) + taken_alone_str = "\n".join(taken_alone) + all_taken_alone_str = "\n".join(all_taken_alone) + print(f"Missing events:\n{missing_events_str}\n") + print(f"Unused events: \n{unused_events_str}") + if len(sys.argv) > 3: + print(f"\n'TakenAlone' events found in perfspect events: \n{taken_alone_str}\n") + print( + f"All 'TakenAlone' events found in perfmon events: \n{all_taken_alone_str}" + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/copyright_go.sh b/scripts/copyright_go.sh new file mode 100755 index 0000000..b3f7487 --- /dev/null +++ b/scripts/copyright_go.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# Define the copyright header +COPYRIGHT_HEADER="// Copyright (C) 2021-2024 Intel Corporation\n// SPDX-License-Identifier: BSD-3-Clause" + +# Loop through all .go files in the current directory and its subdirectories +find . -name "*.go" | while read -r file; do + # Check if the file already contains the copyright header + if ! grep -q "Copyright (C) 2021-2024 Intel Corporation" "$file"; then + # Read the file line by line + { + while IFS= read -r line; do + echo "$line" + if [[ $line == package* ]]; then + echo -e "$COPYRIGHT_HEADER" + fi + done + } < "$file" > temp_file && mv temp_file "$file" + echo "Added copyright header to $file" + else + echo "Copyright header already present in $file" + fi +done diff --git a/scripts/filterperfspectmetrics.py b/scripts/filterperfspectmetrics.py new file mode 100755 index 0000000..4b8822f --- /dev/null +++ b/scripts/filterperfspectmetrics.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# filterperfspectmetrics.py - generates a metrics file based on perfmon metrics and a perfspect metrics file +# +# Usage: filterperfspectmetrics.py +# +# New metrics file in perfspect format is printed to stdout. + +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import json + +# generate metrics file, based on perfmon metrics (allFile) +# usedFile - current perfspect metrics file +# if metric from usedFile not found in perfmon list, +# add it to filtered output as is and add field "origin"="perfspect" +def generate_final_metrics_list(allFile, usedFile): + with open(allFile, "r") as f: + allMetrics = json.load(f) + with open(usedFile, "r") as f: + usedMetrics = json.load(f) + result = [] + for m in allMetrics: + found = find_metric(usedMetrics, m["name"]) + if not found: + print(f"Not including metric {m['name']} from perfmon", file=sys.stderr) + for m in usedMetrics: + found = find_metric(allMetrics, m["name"]) + if found is not None: + result.append(found) + else: + m["origin"] = "perfspect" + print(f"Adding metric {m['name']} from perfspect", file=sys.stderr) + result.append(m) + + print(f"PerfSpect metrics: {len(result)}", file=sys.stderr) + json_object = json.dumps(result, indent=4) + print(json_object) + + +# find metric by name +def find_metric(metrics, metric_name): + for m in metrics: + if m["name"] == metric_name: + return m + return None + + +# arg 1 - all perfmon metrics in "perfspect" style +# arg 2 - pre-existing perfspect metrics file +if __name__ == "__main__": + if len(sys.argv) != 3: + print( + "Usage: filterperfspectmetrics.py ", + file=sys.stderr, + ) + sys.exit(1) + + generate_final_metrics_list(sys.argv[1], sys.argv[2]) diff --git a/scripts/perfmonevents2perfspect.py b/scripts/perfmonevents2perfspect.py new file mode 100755 index 0000000..de1d80e --- /dev/null +++ b/scripts/perfmonevents2perfspect.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# perfmonevents2perfspect.py - generates perfspect formatted events from perfmon events json file and a file of event names +# +# Usage: perfmonevents2perfspect.py +# +# New perfspect formatted events are printed to stdout. + + +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import json + +# arg1 - perfmon event json file (exported from perfmon.intel.com) +# arg2 - file containing list of event names, one event name per line +if __name__ == "__main__": + if len(sys.argv) < 3: + print( + "Usage: perfmonevents2perfspect.py ", + file=sys.stderr, + ) + sys.exit(1) + + # read perfmon json file + with open(sys.argv[1], "r") as f: + perfmon = json.load(f) + + # read list of event names + with open(sys.argv[2], "r") as f: + events = f.readlines() + + # for each event name, find corresponding event in perfmon json and create a perfspect formatted event + # example: cpu/event=0x71,umask=0x00,name='TOPDOWN_FE_BOUND.ALL'/ + # if event name is not found in perfmon json file, it is added to a list of not found events + result = [] + notfound = [] + for e in events: + e = e.strip() + for p in perfmon["Events"]: + if p["EventName"] == e: + # event = f"cpu/event={p['EventCode']},umask={p['UMask']},cmask={p['CMask']},name='{p['EventName']}'/" + event = f"cpu/event={p['EventCode']},umask={p['UMask']},name='{p['EventName']}'/" + result.append(event) + break + else: + notfound.append(e) + + # print perfspect formatted events to stdout + for r in result: + print(r) + + # print the not found event names to stderr + print("\nNot Found Events:", file=sys.stderr) + for n in notfound: + print(n, file=sys.stderr) diff --git a/scripts/perfmonmetrics2perfspect.py b/scripts/perfmonmetrics2perfspect.py new file mode 100755 index 0000000..4a74d47 --- /dev/null +++ b/scripts/perfmonmetrics2perfspect.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# perfmonmetrics2perfspect.py - converts a metrics file to the YAML file format used by PerfSpect 3.0+. +# +# The input metrics file can be one of the following: +# - perfmon metrics json file from github.com/intel/perfmon +# - xml file from perfmon.intel.com or EMON/EDP release +# +# Usage: perfmonmetrics2perfspect.py +# +# New metrics file in perfspect format is printed to stdout. + +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import json + +import xml.etree.ElementTree as ET + +def replace_vars_in_formula(vars, formula): + varMap = { + "[INST_RETIRED.ANY]": "[instructions]", + "[CPU_CLK_UNHALTED.THREAD]": "[cpu-cycles]", + "[CPU_CLK_UNHALTED.REF]": "[ref-cycles]", + "[CPU_CLK_UNHALTED.REF_TSC]": "[ref-cycles]", + "DURATIONTIMEINSECONDS": "1", + "[DURATIONTIMEINMILLISECONDS]": "1000", + "[TOPDOWN.SLOTS:perf_metrics]": "[TOPDOWN.SLOTS]", + "[OFFCORE_REQUESTS_OUTSTANDING.ALL_DATA_RD:c4]": "[OFFCORE_REQUESTS_OUTSTANDING.DATA_RD:c4]", + "[system.tsc_freq]": "[SYSTEM_TSC_FREQ]", + "[system.cha_count/system.socket_count]": "[CHAS_PER_SOCKET]", + "[system.socket_count]": "[SOCKET_COUNT]", + } + newFormula = "" + i = 0 + while i < len(formula): + if formula[i].isalpha() or formula[i] == "_": + x = formula[i] + k = i + 1 + while k < len(formula) and (formula[k].isalpha() or formula[k] == "_"): + x += formula[k] + k += 1 + if vars.get(x) is not None: + newFormula = newFormula + "[" + vars[x] + "]" + else: + newFormula = newFormula + formula[i:k] + i = k + else: + newFormula += formula[i] + i += 1 + for v in varMap: + newFormula = newFormula.replace(v, varMap[v]) + return newFormula + +def translate_perfmon_json_metrics_to_perfspect(inFile): + with open(inFile, "r") as f: + mf = json.load(f) + + if mf.get("Metrics") is None: + print(f"ERROR: No metrics were found in {inFile}", file=sys.stderr) + return + + print(f"Metrics in {inFile}: {len(mf['Metrics'])}", file=sys.stderr) + vars = {} + result = [] + for m in mf["Metrics"]: + vars.clear() + metric = {} + metric["name"] = m["LegacyName"] + # extract the events and constants + for e in m["Events"]: + vars[e["Alias"]] = e["Name"] + for c in m["Constants"]: + vars[c["Alias"]] = c["Name"] + # convert the formula + metric["expression"] = replace_vars_in_formula(vars, m["Formula"]) + result.append(metric) + return result + +# this function has the following known limitations: +# - it does not convert the max notation, e.g., [(val1), (val2)].max +# - it does not convert the list index notation, e.g., val[0][0] +def translate_perfmon_xml_metrics_to_perfspect(inFile): + tree = ET.parse(inFile) + root = tree.getroot() + vars = {} + result = [] + for m in root: + vars.clear() + metric = {} + metric["name"] = m.attrib["name"] + # extract the events and constants + for e in m.findall("event"): + vars[e.attrib["alias"]] = e.text + for c in m.findall("constant"): + vars[c.attrib["alias"]] = c.text + # convert the formula + formula = m.find("formula").text + metric["expression"] = replace_vars_in_formula(vars, formula) + result.append(metric) + + return result + +# translate perfmon metrics file to perfspect style metrics file +# inFile - perfmon_metrics.json file +def translate_perfmon_metrics_to_perfspect(inFile): + # the file can be either a json file or an xml file + fileType = inFile.split(".")[-1] + if fileType == "json": + result = translate_perfmon_json_metrics_to_perfspect(inFile) + elif fileType == "xml": + result = translate_perfmon_xml_metrics_to_perfspect(inFile) + else: + print(f"ERROR: Unsupported file type {fileType}", file=sys.stderr) + return + + print(f"Generated metrics: {len(result)}", file=sys.stderr) + json_object = json.dumps(result, indent=4) + print(json_object) + + +# arg1 - perfmon metrics json file from github.com/intel/perfmon or xml file from perfmon.intel.com or EMON/EDP release +if __name__ == "__main__": + if len(sys.argv) != 2: + print( + "Usage: perfmonmetrics2perfspect.py ", + file=sys.stderr, + ) + sys.exit(1) + + translate_perfmon_metrics_to_perfspect(sys.argv[1]) diff --git a/scripts/print_licenses.sh b/scripts/print_licenses.sh new file mode 100755 index 0000000..35604b0 --- /dev/null +++ b/scripts/print_licenses.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# Run this script from the root of the repository +# Dependencies: jq, go-licenses +# apt install jq +# go install github.com/google/go-licenses@latest + +# List of GitHub projects, must have LICENSE file +github_projects=( + "async-profiler/async-profiler" + "harp-intel/avx-turbo" + "mirror/dmidecode" + "lyonel/lshw" + "pciutils/pciutils" + "intel/pcm" + "ColinIanKing/stress-ng" + "sysstat/sysstat" +) + +# Loop through each URL +for github_project in "${github_projects[@]}"; do + # Fetch the repository data from GitHub API + response=$(curl -s "https://api.github.com/repos/$github_project") + + # Extract the license type using jq + license=$(echo "$response" | jq -r '.license.spdx_id') + + # Print the repository URL and its license type + echo "github.com/$github_project,https://github.com/$github_project/LICENSE,$license" +done + +# non-comforming GitHub repos +echo "github.com/intel/msr-tools,https://github.com/intel/msr-tools/blob/master/cpuid.c,GPL-2.0" +echo "github.com/brendangregg/FlameGraph,https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl,CDDL" +echo "github.com/ipmitool/ipmitool,https://github.com/ipmitool/ipmitool?tab=License-1-ov-file#readme,SUN Microsystems" +echo "github.com/speed47/spectre-meltdown-checker,https://github.com/speed47/spectre-meltdown-checker/blob/master/spectre-meltdown-checker.sh,GPL-3.0-only" + +# repos not in GitHub +echo "etallen.com/cpuid,http://www.etallen.com/cpuid,GPL-2.0" +echo "git.kernel.org/pub/scm/network/ethtool,https://git.kernel.org/pub/scm/network/ethtool/ethtool.git/tree/LICENSE,GPL-2.0" +echo "sourceforge.net/projects/sshpass,https://sourceforge.net/p/sshpass/code-git/ci/main/tree/COPYING,GPL-2.0" +echo "github.com/torvalds/linux/blob/master/tools/power/x86/turbostat,https://github.com/torvalds/linux/blob/master/tools/power/x86/turbostat/turbostat.c,GPL-2.0" +echo "github.com/torvalds/linux/tree/master/tools/perf,https://github.com/torvalds/linux/tree/master/tools/perf,GPL-2.0" + +# Generate a list of licenses for Go dependencies +go-licenses report . 2>/dev/null diff --git a/scripts/targets2yaml.py b/scripts/targets2yaml.py new file mode 100755 index 0000000..e1f76d1 --- /dev/null +++ b/scripts/targets2yaml.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# targets2yaml.py - converts a list of targets in the svr-info 2.x format to the YAML file format used by PerfSpect 3.0+. +# +# Usage: targets2yaml.py < targets > targets.yaml + +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import yaml + +# Read the input file +input = sys.stdin.readlines() +# parse the input file into a list of dictionaries +targets = [] +for line in input: + # Remove leading and trailing whitespace + line = line.strip() + # Skip empty lines + if not line: + continue + # Skip comment lines + if line.startswith('#'): + continue + # Split the line into fields + fields = line.split(':') + target = {} + if len(fields) == 7: + target['name'] = fields[0] + target['host'] = fields[1] + target['port'] = fields[2] + target['user'] = fields[3] + target['key'] = fields[4] + target['pwd'] = fields[5] + #target['sudo'] = fields[6] # not used in PerfSpect 3.0+ + elif len(fields) == 6: + target['name'] = '' + target['host'] = fields[0] + target['port'] = fields[1] + target['user'] = fields[2] + target['key'] = fields[3] + target['pwd'] = fields[4] + #target['sudo'] = fields[5] # not used in PerfSpect 3.0+ + else: + continue + targets.append(target) + +# Write the list of dictionaries to the output file in YAML format +header = '''# This YAML file contains a list of remote targets with their corresponding properties. +# Each target has the following properties: +# name: The name of the target (optional) +# host: The IP address or host name of the target (required) +# port: The port number used to connect to the target via SSH (optional) +# user: The user name used to connect to the target via SSH (optional) +# key: The path to the private key file used to connect to the target via SSH (optional) +# pwd: The password used to connect to the target via SSH (optional) +# +# Note: If key and pwd are both provided, the key will be used for authentication. +# +# Security Notes: +# It is recommended to use a private key for authentication instead of a password. +# Keep this file in a secure location and do not expose it to unauthorized users. +#''' +print(header) +output = {} +output['targets'] = targets +print(yaml.dump(output)) diff --git a/security.md b/security.md deleted file mode 100644 index f14ba1e..0000000 --- a/security.md +++ /dev/null @@ -1,5 +0,0 @@ -# Security Policy -Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. - -## Reporting a Vulnerability -Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). \ No newline at end of file diff --git a/similarity-analyzer/README.md b/similarity-analyzer/README.md deleted file mode 100644 index 0e90ca2..0000000 --- a/similarity-analyzer/README.md +++ /dev/null @@ -1,56 +0,0 @@ -![sim](https://raw.githubusercontent.com/wiki/intel/PerfSpect/sim.png) - -# Similarity Analyzer - -This tool leverages performance telemetry data mainly Topdown Microarchitecture Analysis(TMA) data collected using PerfSpect to generate workload based similarity profiles using Principal Component Analysis(PCA). - -## Prerequisites: - -1. Telemetry **(PerfSpect)** data for the workloads on which you intend to perform similarity analysis on: - * similarity analyzer uses metric_out.average.csv generated by PerfSpect - -2. python version 3+ - -## Dependencies: -please install dependencies mentioned in requirements.txt - -`pip install -r requirements.txt` - -## Usage: - -dopca.py uses csv files to generate similarity analysis of workloads based on telemetry data and generates a similarity chart as shown above. - -1. **run with PerfSpect data**: -``` -python3 dopca.py -f "workload1-average.csv,workload2-average.csv" -o sim-workload1_2 -``` -2. **run with PerfSpect data on Icelake comparing specCPU2017 components** -``` -python3 dopca.py -f "workload1-average.csv" -o sim-workload1 -m ICX -``` -3. **run with PerfSpect data on Cascadelake comparing specCPU2017 components** -``` -python3 dopca.py -f "workload1-average.csv" -o sim-workload1 -m CLX -``` -Please note: telemetry data collected for specCPU2017 components are for reference only. - -4. **run with PerfSpect data for 3 workload profiles with user defined labels** -``` -python3 dopca.py -f "workload1-average.csv,workload2-average.csv,workload3-average.csv" -o sim-workload1 -l "label1,label2,label3" -``` - -## Result: - -The tool currently generates 2 component PCA plot (.png) for the given workloads. - -It also produces a combined CSV file comprising: -1. CPU operating frequency -2. CPU utilization% -3. CPU utilization% in kernel mode -4. CPI -5. All TMA metrics supported on underlying microarchitecture - -## Things to Note - -* Kindly use "--debug" flag if you wish to log PCA components used for plotting. -* Due to mathematical limitation with underlying PCA library, one can perform similarity analysis for 42 workload profiles at the same time. diff --git a/similarity-analyzer/Reference/CLX/500.csv b/similarity-analyzer/Reference/CLX/500.csv deleted file mode 100644 index 8701f94..0000000 --- a/similarity-analyzer/Reference/CLX/500.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.021760174601225,3.067701135,2.92618066,3.71799172 -metric_CPU utilization %,96.72762265938655,99.574020961,1.68502206,100.08312229 -metric_CPU utilization% in kernel mode,0.4534544659509199,0.7573946490000001,0.15828493,0.96520324 -metric_CPI,0.8932584687116566,1.06941018,0.76654094,1.07056462 -metric_kernel_CPI,3.0711944362576697,4.4803015470000025,2.0085456,4.9741571 -metric_L1D MPI (includes data+rfo w/ prefetches),0.006434816871165644,0.010601547000000003,0.00277138,0.01486913 -metric_L1D demand data read hits per instr,0.29129500625766885,0.31985264799999996,0.27030342,0.32009161 -metric_L1-I code read misses (w/ prefetches) per instr,0.007249086319018408,0.017529585,0.00143623,0.02325663 -metric_L2 demand data read hits per instr,0.0040000775460122704,0.007871441,0.00154675,0.00980768 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.0013868141104294474,0.002550281,0.00015482,0.00315505 -metric_L2 demand data read MPI,0.0006924838036809818,0.001675793,1.194e-05,0.00168132 -metric_L2 demand code MPI,0.0001282149079754601,0.00026147200000000044,1.106e-05,0.00074557 -metric_LLC data read MPI (demand+prefetch),0.0009042664417177919,0.00203861,2.894e-05,0.00205369 -metric_LLC total HITM (per instr),7.361963190184047e-08,1.7500000000000028e-07,1e-08,1.5e-06 -metric_LLC total HIT clean line forwards (per instr),2.72760736196319e-07,5.480000000000002e-07,1.2e-07,1.21e-06 -metric_Average LLC data read miss latency (in clks),225.5110796068711,279.06531500900013,192.31646719,571.28654306 -metric_Average LLC data read miss latency (in ns),97.80851949067478,104.92625845,47.08805698,114.84762694 -metric_Average LLC data read miss latency for LOCAL requests (in ns),96.99433335122697,104.58571525900003,45.67860568,110.89230045 -metric_Average LLC data read miss latency for REMOTE requests (in ns),281.2751835176074,300.102309422,153.73426996,323.56020794 -metric_ITLB MPI,1.5489693251533734e-05,2.261300000000001e-05,3.97e-06,6.692e-05 -metric_DTLB 2MB large page load MPI,4.852147239263804e-07,8.250000000000003e-07,9e-08,4.27e-06 -metric_NUMA %_Reads addressed to local DRAM,99.5368831506135,99.94145600299998,95.66864191,99.9421433 -metric_NUMA %_Reads addressed to remote DRAM,0.4631168493865029,2.020856298,0.0578567,4.33135809 -metric_UPI Data transmit BW (MB/sec) (only data),54.16624883926379,76.03431822,6.4845774,132.6407544 -metric_UPI Transmit utilization_% (includes control),0.21526354576687132,0.4544692790000001,0.01979218,1.03743416 -metric_uncore frequency GHz,1.814928319263804,1.961566487,1.6973987,2.39942697 -metric_package power (watts),403.60462576687104,409.4416,116.522,409.676 -metric_DRAM power (watts),27.141435582822087,32.994,20.244,33.816 -metric_core % cycles in non AVX license,99.37649002085892,99.97813513999999,95.25223263,99.98533509 -metric_core % cycles in AVX2 license,0.6235099791411042,2.729407766000001,0.01466491,4.74776737 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),23509.43149294723,46026.213946879994,159.4147712,47248.6365312 -metric_memory bandwidth write (MB/sec),2551.020956662576,3651.549232640001,22.260096,8990.2735744 -metric_memory bandwidth total (MB/sec),26060.452449609813,49186.86782336,181.6748672,52752.5709568 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),0.8967849815950918,3.872299280000001,0.0,40.0271168 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.013970601226993867,0.06292344000000002,0.0,0.0989152 -metric_TMAM_Info_CoreIPC,2.270565249079753,2.516704389,1.86817308,2.60912354 -metric_TMAM_Frontend_Bound(%),33.3838857026994,37.833793402,27.81514829,43.48490061 -metric_TMAM_Bad_Speculation(%),5.514896469263805,8.076683604,2.033219,9.86003549 -metric_TMAM_Backend_bound(%),8.178703425214724,16.673764246,1.61748389,16.76649212 -metric_TMAM_Retiring(%),53.09104189092021,59.194136742,44.02383899,60.89329328 diff --git a/similarity-analyzer/Reference/CLX/502.csv b/similarity-analyzer/Reference/CLX/502.csv deleted file mode 100644 index 05add65..0000000 --- a/similarity-analyzer/Reference/CLX/502.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.2100600887500015,3.3000000325,2.9771538,3.64802062 -metric_CPU utilization %,98.01874215145834,99.861253347,26.81286893,100.05737187 -metric_CPU utilization% in kernel mode,5.227241880486108,16.612244419499998,1.29053333,20.72975897 -metric_CPI,2.225090337847222,3.6326826949999993,1.09947319,5.07358671 -metric_kernel_CPI,2.698570485833335,4.108879793999996,1.81639311,4.80405079 -metric_L1D MPI (includes data+rfo w/ prefetches),0.03087746305555556,0.05788452399999999,0.0074952,0.07703164 -metric_L1D demand data read hits per instr,0.2669194524305555,0.28656478449999995,0.22484928,0.31142832 -metric_L1-I code read misses (w/ prefetches) per instr,0.009772377916666665,0.017624420000000002,5.734e-05,0.02514456 -metric_L2 demand data read hits per instr,0.009278243055555553,0.011165290999999999,0.00132956,0.04070061 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.021455055833333334,0.0461411215,0.00617346,0.06137691 -metric_L2 demand data read MPI,0.004057715763888888,0.006717267000000001,0.00070597,0.01034895 -metric_L2 demand code MPI,0.0008843083333333329,0.002858764999999999,4.806e-05,0.00337589 -metric_LLC data read MPI (demand+prefetch),0.009850233749999998,0.02228690899999999,0.00064215,0.02730925 -metric_LLC total HITM (per instr),5.650416666666667e-06,2.6334e-05,3e-08,3.62e-05 -metric_LLC total HIT clean line forwards (per instr),1.6221805555555556e-05,6.0886999999999995e-05,8.7e-07,8.982e-05 -metric_Average LLC data read miss latency (in clks),342.6333947171525,542.9694790005,227.60826068,631.30465091 -metric_Average LLC data read miss latency (in ns),130.9522505870833,184.81838485049997,101.02051549,209.02881675 -metric_Average LLC data read miss latency for LOCAL requests (in ns),130.80914499590278,184.69148966199998,101.35491878,209.02408315 -metric_Average LLC data read miss latency for REMOTE requests (in ns),242.94396848374987,288.354342977,164.064981,312.28660439 -metric_ITLB MPI,8.613347222222225e-05,0.00022148949999999996,6.4e-07,0.00036929 -metric_DTLB 2MB large page load MPI,1.087638888888889e-05,4.484199999999999e-05,1.29e-06,8.719e-05 -metric_NUMA %_Reads addressed to local DRAM,99.86285472395829,99.969117666,99.39036102,99.97977282 -metric_NUMA %_Reads addressed to remote DRAM,0.13714527604166674,0.3913292875,0.02022718,0.60963898 -metric_UPI Data transmit BW (MB/sec) (only data),456.5035110125001,910.91438298,86.4083556,1037.0798046 -metric_UPI Transmit utilization_% (includes control),9.582844336388884,40.08729298949999,0.30833053,47.18783987 -metric_uncore frequency GHz,2.0598683988888875,2.3552768879999997,1.7333878,2.3990957 -metric_package power (watts),406.8044444444444,409.7488,250.578,410.366 -metric_DRAM power (watts),49.727680555555565,60.00279999999999,28.42,60.594 -metric_core % cycles in non AVX license,99.5402217838194,99.98447580550001,98.13806976,100.0 -metric_core % cycles in AVX2 license,0.4597782161805555,1.5523601715,0.0,1.86193024 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),109603.14278124443,212641.64504255992,19669.8659072,223078.8039296 -metric_memory bandwidth write (MB/sec),29813.292250666676,82275.95156991998,2848.02048,101754.2425088 -metric_memory bandwidth total (MB/sec),139416.43503191118,219083.82646143995,29485.8556672,225926.8244096 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),9.997166238888887,57.12421967999998,0.0,122.8519536 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.01732935,0.05733807999999999,0.0,0.0768664 -metric_TMAM_Info_CoreIPC,1.0154542925000003,1.6791690595,0.39419845,1.81905299 -metric_TMAM_Frontend_Bound(%),18.050301653333324,32.400662229,4.13374503,35.32494346 -metric_TMAM_Bad_Speculation(%),5.436452479861108,15.4910095745,1.59540293,16.64138029 -metric_TMAM_Backend_bound(%),53.015591620347244,73.2000857845,7.19575517,81.2068256 -metric_TMAM_Retiring(%),23.551329809374987,38.512896852,11.38105636,41.58771201 diff --git a/similarity-analyzer/Reference/CLX/505.csv b/similarity-analyzer/Reference/CLX/505.csv deleted file mode 100644 index 238ed30..0000000 --- a/similarity-analyzer/Reference/CLX/505.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.2526650650510196,3.300001575,3.02438521,3.67674977 -metric_CPU utilization %,98.39995316321425,100.29416892,36.21843239,100.51448384 -metric_CPU utilization% in kernel mode,1.6004654866326526,2.1581800825,0.29940189,9.90692019 -metric_CPI,5.259176341122453,8.3818103675,2.39653307,12.0749793 -metric_kernel_CPI,4.01239085,5.32236844,2.04677578,10.18719484 -metric_L1D MPI (includes data+rfo w/ prefetches),0.08546621341836744,0.124075645,0.04446473,0.15183242 -metric_L1D demand data read hits per instr,0.2619970762244899,0.292355965,0.16023982,0.31040895 -metric_L1-I code read misses (w/ prefetches) per instr,0.00010305015306122447,0.0001607575,5.018e-05,0.00021122 -metric_L2 demand data read hits per instr,0.028519476683673457,0.063611895,0.00577868,0.07969229 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.05780424704081635,0.0878067825,0.01803533,0.1106554 -metric_L2 demand data read MPI,0.01204966841836735,0.019213175,0.0050159,0.03868565 -metric_L2 demand code MPI,0.00010025831632653064,0.0001572725,4.747e-05,0.00020471 -metric_LLC data read MPI (demand+prefetch),0.03981987040816325,0.0653667125,0.00971433,0.08328221 -metric_LLC total HITM (per instr),2.7892857142857144e-07,4.625e-07,2e-08,1.923e-05 -metric_LLC total HIT clean line forwards (per instr),2.5369897959183666e-06,3.4900000000000005e-06,8.4e-07,6.977e-05 -metric_Average LLC data read miss latency (in clks),572.2540468387753,743.5120594225,237.37690635,769.91809767 -metric_Average LLC data read miss latency (in ns),207.356760214847,258.7868181525,106.95423025,265.46774498 -metric_Average LLC data read miss latency for LOCAL requests (in ns),207.33568838306118,258.508053195,106.64340857,265.22031732 -metric_Average LLC data read miss latency for REMOTE requests (in ns),369.15680997699,396.01552603249996,179.07150109,422.1106139 -metric_ITLB MPI,2.302346938775509e-06,3.4325000000000006e-06,1.26e-06,4.74e-06 -metric_DTLB 2MB large page load MPI,5.2306122448979594e-06,1.17625e-05,1.6e-07,1.872e-05 -metric_NUMA %_Reads addressed to local DRAM,99.97082430602036,99.97808297,99.88798798,99.98040376 -metric_NUMA %_Reads addressed to remote DRAM,0.02917569397959185,0.035710149999999996,0.01959624,0.11201202 -metric_UPI Data transmit BW (MB/sec) (only data),105.19396502142851,121.46522535,80.9563104,558.3809322 -metric_UPI Transmit utilization_% (includes control),0.43674143178571406,0.7099175425,0.20817188,8.25500305 -metric_uncore frequency GHz,2.2276688301530623,2.408621345,1.92075826,2.41753203 -metric_package power (watts),404.1126632653061,411.304,314.012,412.716 -metric_DRAM power (watts),56.57315306122448,60.7605,39.138,61.248 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),186724.11360848992,215160.4750304,74172.977792,226298.1284224 -metric_memory bandwidth write (MB/sec),22647.71428244899,46381.014752,543.6218112,58650.1949568 -metric_memory bandwidth total (MB/sec),209371.82789093885,226669.57736,85495.908416,230170.8742784 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),0.048730469387755125,0.1969316,0.0,0.4531504 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.013173828571428575,0.0373866,0.0,0.0624832 -metric_TMAM_Info_CoreIPC,0.42919180081632646,0.71015821,0.16563175,0.83453887 -metric_TMAM_Frontend_Bound(%),11.02227430673469,19.99570653,4.36975762,21.37918606 -metric_TMAM_Bad_Speculation(%),15.326036203979598,37.003613105,3.82069575,46.78937543 -metric_TMAM_Backend_bound(%),64.40427346209185,83.57133383499999,18.41216999,86.90259384 -metric_TMAM_Retiring(%),9.269098091632651,14.75125361,3.71613403,17.00957226 diff --git a/similarity-analyzer/Reference/CLX/520.csv b/similarity-analyzer/Reference/CLX/520.csv deleted file mode 100644 index 591e17e..0000000 --- a/similarity-analyzer/Reference/CLX/520.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.3059233672300494,3.299997474,3.29854725,3.66136637 -metric_CPU utilization %,97.95825656262915,99.644940216,4.08560591,100.47846782 -metric_CPU utilization% in kernel mode,0.7645479876056334,1.0057095999999999,0.22587879,1.13822709 -metric_CPI,3.35778194568075,3.636378818,1.37790403,3.72711869 -metric_kernel_CPI,4.638268094976529,7.732682195999999,1.72176027,10.88386283 -metric_L1D MPI (includes data+rfo w/ prefetches),0.042677431690140814,0.046198728,0.02265509,0.04777613 -metric_L1D demand data read hits per instr,0.3046136366666667,0.30565739999999997,0.28808604,0.30574846 -metric_L1-I code read misses (w/ prefetches) per instr,0.0009424950704225347,0.001308172,0.00078617,0.00298067 -metric_L2 demand data read hits per instr,0.013400573661971822,0.013823085999999998,0.00749518,0.0141285 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.019780088920187795,0.021489130000000002,0.01074967,0.02206627 -metric_L2 demand data read MPI,0.00913390582159624,0.010005092,0.00474636,0.0102213 -metric_L2 demand code MPI,0.00034370276995305166,0.00040004799999999996,0.00030716,0.00048564 -metric_LLC data read MPI (demand+prefetch),0.013591926384976522,0.014855502,0.00473967,0.01535435 -metric_LLC total HITM (per instr),6.4507042253521e-08,8.399999999999977e-08,3e-08,2.17e-06 -metric_LLC total HIT clean line forwards (per instr),2.032629107981219e-06,2.52e-06,5.6e-07,2.61e-06 -metric_Average LLC data read miss latency (in clks),324.9979443143194,329.65573342799996,260.3525125,330.38315113 -metric_Average LLC data read miss latency (in ns),119.63054926624412,121.318946678,92.43707616,121.51923424 -metric_Average LLC data read miss latency for LOCAL requests (in ns),119.51429906863851,121.20410489,91.93442253,121.45140326 -metric_Average LLC data read miss latency for REMOTE requests (in ns),338.8665925371361,349.80652722599996,165.02286008,352.83049215 -metric_ITLB MPI,0.00014267117370892027,0.000188556,5.648e-05,0.0001947 -metric_DTLB 2MB large page load MPI,6.317605633802816e-06,1.2415999999999998e-05,6.7e-07,1.385e-05 -metric_NUMA %_Reads addressed to local DRAM,99.94001982474171,99.946774084,99.39167925,99.94797123 -metric_NUMA %_Reads addressed to remote DRAM,0.05998017525821597,0.05875254799999999,0.05202877,0.60832075 -metric_UPI Data transmit BW (MB/sec) (only data),119.72412888169012,126.94949064,35.4493944,264.3020226 -metric_UPI Transmit utilization_% (includes control),0.31587580798122056,0.370246552,-0.14861202,0.76969975 -metric_uncore frequency GHz,2.1496576050234757,2.195915416,2.11136934,2.3976484 -metric_package power (watts),405.9544319248826,409.5268,131.506,409.61 -metric_DRAM power (watts),51.401971830985886,52.21639999999999,21.762,52.282 -metric_core % cycles in non AVX license,99.9999931259155,100.0,99.99853582,100.0 -metric_core % cycles in AVX2 license,6.874084507042254e-06,0.0,0.0,0.00146418 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),105387.41505454271,108327.59920384,4408.5167488,108461.358272 -metric_memory bandwidth write (MB/sec),37395.63846448075,39182.820753919994,1577.4437888,39313.4812928 -metric_memory bandwidth total (MB/sec),142783.05351902347,147463.01787648,5985.9605376,147766.0421632 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),1.4024422610328637,0.2651017599999991,0.0,238.1336584 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.014975444131455407,0.04251264,0.0,0.1443112 -metric_TMAM_Info_CoreIPC,0.6001402040845074,0.609624128,0.53660754,1.45147989 -metric_TMAM_Frontend_Bound(%),16.895021648497654,19.035690566,15.10663654,22.12625982 -metric_TMAM_Bad_Speculation(%),6.671377292488264,6.820131794,6.23407472,7.50520548 -metric_TMAM_Backend_bound(%),61.79154890253517,63.326513998,52.20756825,63.40895836 -metric_TMAM_Retiring(%),14.68375349192489,14.994609405999999,13.16942127,26.00481187 diff --git a/similarity-analyzer/Reference/CLX/523.csv b/similarity-analyzer/Reference/CLX/523.csv deleted file mode 100644 index a586a60..0000000 --- a/similarity-analyzer/Reference/CLX/523.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.113664802149532,3.2981167110000005,2.91315788,3.29999811 -metric_CPU utilization %,98.40737461196261,100.056965506,25.74607143,100.61428368 -metric_CPU utilization% in kernel mode,1.229053109345794,1.5567897699999997,0.6463919,1.86906396 -metric_CPI,3.000982962429908,7.638152114999991,0.78179727,9.88317335 -metric_kernel_CPI,4.594238649906543,7.614691970999998,1.45099568,8.08982276 -metric_L1D MPI (includes data+rfo w/ prefetches),0.04409093523364488,0.049719449,0.03134833,0.05459406 -metric_L1D demand data read hits per instr,0.23774874233644855,0.277659715,0.2236496,0.27834167 -metric_L1-I code read misses (w/ prefetches) per instr,0.0026935839252336456,0.006014066999999998,4.452e-05,0.00741845 -metric_L2 demand data read hits per instr,0.016632106542056067,0.026342442,0.00132722,0.02681967 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.030271151682242974,0.08084536299999996,0.00256444,0.11034758 -metric_L2 demand data read MPI,0.004719442056074767,0.018346874999999985,0.00015186,0.02130617 -metric_L2 demand code MPI,0.0005723329906542057,0.0016451979999999992,4.325e-05,0.00283672 -metric_LLC data read MPI (demand+prefetch),0.0253768370093458,0.07112708499999991,0.00121177,0.09586089 -metric_LLC total HITM (per instr),1.125233644859812e-07,2.8699999999999986e-07,2e-08,5.3e-07 -metric_LLC total HIT clean line forwards (per instr),1.5737383177570088e-06,3.7989999999999953e-06,1.8e-07,1.213e-05 -metric_Average LLC data read miss latency (in clks),409.96719983672887,613.203864971,248.38386961,655.05414125 -metric_Average LLC data read miss latency (in ns),154.11684542177565,214.251682162,92.13620893,215.88025926 -metric_Average LLC data read miss latency for LOCAL requests (in ns),153.9614712924299,214.240257319,91.83606671,215.9514786 -metric_Average LLC data read miss latency for REMOTE requests (in ns),287.1126283285982,306.401082525,156.2211316,311.98038333 -metric_ITLB MPI,4.802738317757008e-05,0.000135915,5.3e-07,0.00018375 -metric_DTLB 2MB large page load MPI,2.319626168224299e-06,6.964999999999981e-06,4.7e-07,9.05e-06 -metric_NUMA %_Reads addressed to local DRAM,99.9113170775701,99.97564144,99.813382,99.97659332 -metric_NUMA %_Reads addressed to remote DRAM,0.08868292242990652,0.16041447999999997,0.02340668,0.186618 -metric_UPI Data transmit BW (MB/sec) (only data),135.57829626168225,228.03413219999993,25.3187046,360.2529252 -metric_UPI Transmit utilization_% (includes control),0.5053190269158877,0.9204644479999994,0.09662265,1.11518157 -metric_uncore frequency GHz,2.0550753408411215,2.4048183229999998,1.71599206,2.40646629 -metric_package power (watts),392.9480934579439,411.346,224.26,412.832 -metric_DRAM power (watts),47.321158878504676,60.1034,27.238,60.358 -metric_core % cycles in non AVX license,95.16717574682244,100.0,64.30864477,100.0 -metric_core % cycles in AVX2 license,4.832824253177572,20.092547911999997,0.0,35.69135523 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),136226.43479781688,227717.70929664,18204.077184,231909.6043648 -metric_memory bandwidth write (MB/sec),15862.20581945421,31311.304511999995,160.5855744,35058.51008 -metric_memory bandwidth total (MB/sec),152088.64061727104,228526.3957632,27446.5776256,232071.6511616 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),14.569027469158875,113.0554395199998,0.0,242.4802992 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.025702594392523388,0.07735311999999997,0.0,0.184588 -metric_TMAM_Info_CoreIPC,1.4602571710280368,2.438102807,0.20236415,2.55820795 -metric_TMAM_Frontend_Bound(%),17.42697422971963,26.984376851999997,3.58461328,32.90628338 -metric_TMAM_Bad_Speculation(%),1.6774295500000003,2.9379000470000003,0.12829469,3.72300333 -metric_TMAM_Backend_bound(%),50.932177305046714,89.30548880399999,14.03612695,91.66832517 -metric_TMAM_Retiring(%),30.009999290560756,50.764708061,3.99989013,52.72232084 diff --git a/similarity-analyzer/Reference/CLX/525.csv b/similarity-analyzer/Reference/CLX/525.csv deleted file mode 100644 index 1e04812..0000000 --- a/similarity-analyzer/Reference/CLX/525.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.0056125687912085,2.99612063,2.9699711,3.7018681 -metric_CPU utilization %,96.96496717065938,99.47614933,13.45525297,100.00941778 -metric_CPU utilization% in kernel mode,0.776202716043956,1.482852055,0.2933457,1.83951802 -metric_CPI,0.7031174083516483,0.7127994449999999,0.68698569,0.72958766 -metric_kernel_CPI,4.68152220824176,6.8944410099999995,1.8614167,8.30133333 -metric_L1D MPI (includes data+rfo w/ prefetches),0.007037417142857142,0.007789084999999999,0.0052604,0.00796019 -metric_L1D demand data read hits per instr,0.22616498076923086,0.23724024,0.21944862,0.23819277 -metric_L1-I code read misses (w/ prefetches) per instr,0.005473947032967033,0.0066476,0.00258158,0.00715116 -metric_L2 demand data read hits per instr,0.003145434945054945,0.003667455,0.00213431,0.00380131 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.000973426043956044,0.001563575,0.00076626,0.00169035 -metric_L2 demand data read MPI,0.00017337725274725274,0.00035297,0.00010075,0.00038605 -metric_L2 demand code MPI,6.347703296703297e-05,8.2e-05,4.719e-05,9.17e-05 -metric_LLC data read MPI (demand+prefetch),0.0004912445054945055,0.0007563100000000001,0.00037594,0.00078371 -metric_LLC total HITM (per instr),3.318681318681314e-08,8e-08,1e-08,4.6e-07 -metric_LLC total HIT clean line forwards (per instr),3.3472527472527497e-07,7.6e-07,1.9e-07,1.04e-06 -metric_Average LLC data read miss latency (in clks),220.63452676648348,235.126171495,214.58588463,245.52660902 -metric_Average LLC data read miss latency (in ns),101.36308203703295,107.094554955,85.21974631,108.53964318 -metric_Average LLC data read miss latency for LOCAL requests (in ns),99.21681776527475,103.873847455,84.36921809,105.15825633 -metric_Average LLC data read miss latency for REMOTE requests (in ns),192.57787686659344,201.40736184000002,167.30684625,210.81941905 -metric_ITLB MPI,2.0242857142857147e-06,2.8900000000000003e-06,1.27e-06,3.75e-06 -metric_DTLB 2MB large page load MPI,6.939560439560436e-07,1.495e-06,3.6e-07,1.67e-06 -metric_NUMA %_Reads addressed to local DRAM,97.63715822362639,98.48401175000001,95.30582518,99.23121727 -metric_NUMA %_Reads addressed to remote DRAM,2.362841776373626,3.817850095,0.76878273,4.69417482 -metric_UPI Data transmit BW (MB/sec) (only data),406.72427718461546,945.1555848,40.3994178,1148.3668476 -metric_UPI Transmit utilization_% (includes control),1.7954831840659338,4.214006835,0.12348527,5.02306199 -metric_uncore frequency GHz,1.773455074175824,1.838003465,1.72445699,2.40132565 -metric_package power (watts),404.95560439560455,409.39,191.65,409.436 -metric_DRAM power (watts),27.320725274725266,32.051,21.276,33.506 -metric_core % cycles in non AVX license,99.54999721824174,99.87571774,98.07447433,99.91532536 -metric_core % cycles in AVX2 license,0.44997467087912085,1.4344104249999998,0.08467464,1.92552567 -metric_core % cycles in AVX-512 license,2.8110879120879118e-05,0.0,0.0,0.00130578 -metric_memory bandwidth read (MB/sec),21657.72730711209,36945.3720576,3327.850688,39202.1252224 -metric_memory bandwidth write (MB/sec),5584.570024650549,14676.360409600002,726.8444928,16985.9322624 -metric_memory bandwidth total (MB/sec),27242.297331762627,51621.732467199996,4054.6951808,56188.0574848 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),8.646389046153846,39.5933564,0.0,78.1911224 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.011940465934065931,0.0411484,0.0,0.0529304 -metric_TMAM_Info_CoreIPC,2.8447100154945044,2.886310295,2.74127444,2.91126878 -metric_TMAM_Frontend_Bound(%),18.178104352857154,19.208098345,14.1131154,19.53357187 -metric_TMAM_Bad_Speculation(%),3.102275578021977,3.8461306950000003,1.17024467,3.90389941 -metric_TMAM_Backend_bound(%),5.65677420956044,8.3947507,4.46263352,9.13438721 -metric_TMAM_Retiring(%),73.09406218000001,74.62933741500001,71.04251109,75.63151914 diff --git a/similarity-analyzer/Reference/CLX/531.csv b/similarity-analyzer/Reference/CLX/531.csv deleted file mode 100644 index 9dc1223..0000000 --- a/similarity-analyzer/Reference/CLX/531.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.046353640194174,3.063076341,3.03368664,3.19129682 -metric_CPU utilization %,98.66499121999995,99.39864658,57.42747798,99.88809801 -metric_CPU utilization% in kernel mode,1.6259417512621361,1.814351181,1.33057931,1.89343418 -metric_CPI,0.9946628399029125,1.041380352,0.96072314,1.0773468 -metric_kernel_CPI,1.8023395968932046,1.922174126,1.24174417,2.02605486 -metric_L1D MPI (includes data+rfo w/ prefetches),0.009446953592233008,0.010174039999999999,0.00815066,0.01072998 -metric_L1D demand data read hits per instr,0.2264787723300972,0.233055595,0.21621502,0.24021191 -metric_L1-I code read misses (w/ prefetches) per instr,0.004643957475728156,0.005573976,0.00338208,0.00587104 -metric_L2 demand data read hits per instr,0.005466412912621362,0.005991585,0.00450126,0.0060977 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.001228791262135923,0.0019037239999999997,0.00100443,0.00198012 -metric_L2 demand data read MPI,0.00036931689320388354,0.00040751,0.00031411,0.00041509 -metric_L2 demand code MPI,2.9460776699029124e-05,3.6672000000000004e-05,2.506e-05,4.45e-05 -metric_LLC data read MPI (demand+prefetch),0.0009858163106796112,0.001167385,0.00073229,0.00132476 -metric_LLC total HITM (per instr),2.2330097087378632e-08,2e-08,1e-08,9.3e-07 -metric_LLC total HIT clean line forwards (per instr),1.1436893203883505e-07,3.1899999999999993e-07,6e-08,3.6e-07 -metric_Average LLC data read miss latency (in clks),219.3407355995146,227.7829841,213.07901609,233.81428701 -metric_Average LLC data read miss latency (in ns),94.89167519640775,97.81479416100001,85.88263231,102.4875255 -metric_Average LLC data read miss latency for LOCAL requests (in ns),94.53663126495148,97.643421222,85.63595722,100.8491219 -metric_Average LLC data read miss latency for REMOTE requests (in ns),411.5239586847573,444.492409089,262.53266337,451.21870539 -metric_ITLB MPI,8.307184466019415e-06,1.0067e-05,6.19e-06,1.097e-05 -metric_DTLB 2MB large page load MPI,8.639805825242718e-07,9.69e-07,6.3e-07,1.35e-06 -metric_NUMA %_Reads addressed to local DRAM,99.88222663650491,99.909944253,99.79720613,99.92891056 -metric_NUMA %_Reads addressed to remote DRAM,0.11777336349514562,0.14738483399999996,0.07108944,0.20279387 -metric_UPI Data transmit BW (MB/sec) (only data),45.268729759223284,56.410908119999995,42.151284,60.3598086 -metric_UPI Transmit utilization_% (includes control),1.2618151795145631,10.225422923,0.10387779,11.79633646 -metric_uncore frequency GHz,1.9062411970873778,1.9379562369999999,1.79716317,2.14969752 -metric_package power (watts),408.48467961165045,409.432,321.72,409.494 -metric_DRAM power (watts),28.600000000000012,31.587600000000002,25.79,31.892 -metric_core % cycles in non AVX license,99.99124035330097,100.0,99.78070818,100.0 -metric_core % cycles in AVX2 license,0.008759646699029126,0.07816307999999993,0.0,0.21929182 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),23310.78980871457,25264.17212544,15944.1156864,26097.4891392 -metric_memory bandwidth write (MB/sec),6850.2590415534005,20432.701172479996,3830.8617088,20985.7651584 -metric_memory bandwidth total (MB/sec),30161.04885026797,42978.12350976,19774.9773952,43881.4645248 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),0.2707492116504854,0.7667002399999989,0.0,9.5492144 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.018997902912621354,0.05577112,0.0,0.0755912 -metric_TMAM_Info_CoreIPC,2.011586464757282,2.0745533729999996,1.85641243,2.08176519 -metric_TMAM_Frontend_Bound(%),27.012636639320387,27.815260707,25.34919566,28.5864563 -metric_TMAM_Bad_Speculation(%),14.164869466407769,15.320783408,11.35056948,15.55543109 -metric_TMAM_Backend_bound(%),9.490565156601946,12.429051241999996,8.29304726,15.45481814 -metric_TMAM_Retiring(%),49.36246450805826,50.70269041,45.42330475,51.15080918 diff --git a/similarity-analyzer/Reference/CLX/541.csv b/similarity-analyzer/Reference/CLX/541.csv deleted file mode 100644 index a1ee0dc..0000000 --- a/similarity-analyzer/Reference/CLX/541.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.0716010301973693,3.084521781,3.03515053,3.13021907 -metric_CPU utilization %,98.78039712447365,99.5520550455,57.24924435,99.58080163 -metric_CPU utilization% in kernel mode,0.2181633266447367,0.259204461,0.17274306,0.34237352 -metric_CPI,1.2363216828947368,1.273254635,1.11044618,1.28943208 -metric_kernel_CPI,3.9563425359868436,5.080399164499999,2.41022481,5.30828038 -metric_L1D MPI (includes data+rfo w/ prefetches),0.006064294210526314,0.0084036505,0.00446946,0.01169192 -metric_L1D demand data read hits per instr,0.25809237901315796,0.26596718350000004,0.23618889,0.26706696 -metric_L1-I code read misses (w/ prefetches) per instr,7.222289473684213e-05,0.0001047035,5.023e-05,0.00014767 -metric_L2 demand data read hits per instr,0.003017264539473684,0.0034335554999999998,0.00267186,0.00449478 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.0006344203947368421,0.0015362519999999992,0.00010602,0.00291705 -metric_L2 demand data read MPI,7.993223684210529e-05,0.00017849049999999996,2.072e-05,0.00033297 -metric_L2 demand code MPI,1.877236842105263e-05,3.1886e-05,8.58e-06,6.356e-05 -metric_LLC data read MPI (demand+prefetch),0.0001249128947368421,0.00029422249999999975,2.194e-05,0.00055534 -metric_LLC total HITM (per instr),3.611842105263155e-08,5e-08,2e-08,2.2e-07 -metric_LLC total HIT clean line forwards (per instr),1.4921052631578958e-07,2.844999999999999e-07,7e-08,5.1e-07 -metric_Average LLC data read miss latency (in clks),252.05950394164478,381.1425082195,214.15573434,514.47330551 -metric_Average LLC data read miss latency (in ns),99.8855728728948,102.1555806945,92.06657869,102.90796472 -metric_Average LLC data read miss latency for LOCAL requests (in ns),97.8642298977632,100.07791293449999,91.62543723,100.83009663 -metric_Average LLC data read miss latency for REMOTE requests (in ns),261.16970242414493,290.41684569999995,219.44794154,315.6041849 -metric_ITLB MPI,1.3285789473684208e-05,2.673199999999998e-05,8.66e-06,0.00010335 -metric_DTLB 2MB large page load MPI,3.3322368421052637e-07,5.5e-07,7e-08,9e-07 -metric_NUMA %_Reads addressed to local DRAM,98.73484449907897,99.435905209,97.54096517,99.6487823 -metric_NUMA %_Reads addressed to remote DRAM,1.2651555009210533,2.0622632994999996,0.3512177,2.45903483 -metric_UPI Data transmit BW (MB/sec) (only data),41.83765623947368,56.66735025,22.788045,67.254201 -metric_UPI Transmit utilization_% (includes control),0.11588477730263158,0.1666355645,0.06256116,0.22389168 -metric_uncore frequency GHz,1.8923169426315793,1.9613795415,1.79702835,2.14050201 -metric_package power (watts),408.72477631578954,409.352,324.486,409.386 -metric_DRAM power (watts),21.227460526315777,23.780799999999996,18.072,26.37 -metric_core % cycles in non AVX license,99.99891923269739,100.0,99.99378629,100.0 -metric_core % cycles in AVX2 license,0.0010807673026315787,0.003160968999999999,0.0,0.00621371 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),4268.159922442105,9720.831640959994,964.8681216,17692.0651904 -metric_memory bandwidth write (MB/sec),2066.395902989474,4714.865426559998,452.8207872,8715.3611392 -metric_memory bandwidth total (MB/sec),6334.555825431581,14470.34972287999,1417.6889088,26407.4263296 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),0.2590700052631578,1.2615716799999994,0.0,7.3578752 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.004946331578947367,0.014031759999999999,0.0,0.0360832 -metric_TMAM_Info_CoreIPC,1.6185106405921053,1.681410931,1.55107046,1.80107784 -metric_TMAM_Frontend_Bound(%),24.440978589013167,25.399770751,23.28037727,29.43835693 -metric_TMAM_Bad_Speculation(%),29.384086120986854,30.4499842755,22.62885311,30.61240346 -metric_TMAM_Backend_bound(%),6.654114449539474,7.046533495,5.69599344,7.98459251 -metric_TMAM_Retiring(%),39.55786185855263,41.470829981,35.5593307,45.47371867 diff --git a/similarity-analyzer/Reference/CLX/548.csv b/similarity-analyzer/Reference/CLX/548.csv deleted file mode 100644 index 603c927..0000000 --- a/similarity-analyzer/Reference/CLX/548.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.040034321898735,3.0461862109999998,3.03238287,3.07797014 -metric_CPU utilization %,99.30960854594939,99.45531254549999,81.78105598,99.67674323 -metric_CPU utilization% in kernel mode,0.1459909841772152,0.1581892475,0.13279029,0.17431968 -metric_CPI,1.0136686526582286,1.0311823165,0.92633388,1.03779345 -metric_kernel_CPI,3.918524672911394,4.344634461,2.69944947,4.45623191 -metric_L1D MPI (includes data+rfo w/ prefetches),2.1274873417721506e-05,3.0334500000000006e-05,1.479e-05,4.195e-05 -metric_L1D demand data read hits per instr,0.3110287584810125,0.31519440200000004,0.28733714,0.31640138 -metric_L1-I code read misses (w/ prefetches) per instr,9.80820253164557e-05,0.00029394,1.318e-05,0.00060311 -metric_L2 demand data read hits per instr,1.004936708860759e-05,1.2691000000000001e-05,7.84e-06,1.454e-05 -metric_L2 MPI (includes code+data+rfo w/ prefetches),3.7598734177215194e-06,4.2745e-06,3.1e-06,9.5e-06 -metric_L2 demand data read MPI,9.889240506329112e-07,1.14e-06,8.6e-07,1.87e-06 -metric_L2 demand code MPI,1.087341772151898e-07,1.4e-07,2e-08,3.43e-06 -metric_LLC data read MPI (demand+prefetch),1.4588607594936717e-07,1.9e-07,1e-07,1.14e-06 -metric_LLC total HITM (per instr),3.1518987341772075e-08,7e-08,2e-08,8e-08 -metric_LLC total HIT clean line forwards (per instr),1.1708860759493686e-07,1.5e-07,9e-08,1.7e-07 -metric_Average LLC data read miss latency (in clks),964.2632211182286,555.916779441,310.816771,37720.04520818 -metric_Average LLC data read miss latency (in ns),226.769229213924,303.718621206,67.04004162,336.10149645 -metric_Average LLC data read miss latency for LOCAL requests (in ns),216.29557306588603,244.457896312,41.52803892,409.76901585 -metric_Average LLC data read miss latency for REMOTE requests (in ns),230.5190445052532,347.822490147,196.45276429,395.86908872 -metric_ITLB MPI,1.4017721518987339e-06,1.5715e-06,1.23e-06,1.77e-06 -metric_DTLB 2MB large page load MPI,3.79746835443038e-10,0.0,0.0,4e-08 -metric_NUMA %_Reads addressed to local DRAM,41.44528870373417,54.453423933,24.56903097,83.67310226 -metric_NUMA %_Reads addressed to remote DRAM,58.554711296265836,69.10628541,16.32689774,75.43096903 -metric_UPI Data transmit BW (MB/sec) (only data),4.993361737974682,5.49007074,4.2053004,8.3372814 -metric_UPI Transmit utilization_% (includes control),0.01570783253164558,0.0172494225,0.01364033,0.02542483 -metric_uncore frequency GHz,1.791579613227848,1.79561558,1.77760842,1.88589538 -metric_package power (watts),409.0898354430383,409.2623,391.19,409.39 -metric_DRAM power (watts),20.162556962025313,20.315999999999995,19.982,20.36 -metric_core % cycles in non AVX license,99.9898699978481,100.0,99.90718498,100.0 -metric_core % cycles in AVX2 license,0.010130002151898732,0.048661323000000006,0.0,0.09281502 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),27.281203686075948,30.297763200000002,24.1256448,45.5367168 -metric_memory bandwidth write (MB/sec),10.682144810126584,11.40846464,9.9717888,15.5509248 -metric_memory bandwidth total (MB/sec),37.963348496202514,41.77243456,34.43712,61.0876416 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),0.07047955949367089,0.2394461600000001,0.0,2.4602576 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.012851129113924055,0.03293016,0.0,0.0688784 -metric_TMAM_Info_CoreIPC,1.9734398038607603,2.0167885534999996,1.92716575,2.15904875 -metric_TMAM_Frontend_Bound(%),32.84216134455695,33.6464251205,28.29886531,37.62113467 -metric_TMAM_Bad_Speculation(%),7.462978091835441,9.2762376195,6.23892559,12.14669766 -metric_TMAM_Backend_bound(%),1.9631327961392413,2.4305130965,1.66403253,2.88716794 -metric_TMAM_Retiring(%),57.77221118126584,58.4789412625,56.69330778,58.83800938 diff --git a/similarity-analyzer/Reference/CLX/557.csv b/similarity-analyzer/Reference/CLX/557.csv deleted file mode 100644 index cea709e..0000000 --- a/similarity-analyzer/Reference/CLX/557.csv +++ /dev/null @@ -1,42 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),3.202889360965517,3.299993554,3.05976137,3.65346708 -metric_CPU utilization %,98.78878638717242,99.59280693,25.41652772,99.88923664 -metric_CPU utilization% in kernel mode,2.2420638786206903,2.540794661999999,1.2418784,13.17799569 -metric_CPI,1.4914782437931031,2.489547734,0.76256473,2.57654999 -metric_kernel_CPI,2.2333072481379315,2.653812574,1.44003521,3.95285449 -metric_L1D MPI (includes data+rfo w/ prefetches),0.015596114827586206,0.024280138,0.00198423,0.02564399 -metric_L1D demand data read hits per instr,0.2341535811724138,0.251135414,0.10484604,0.25425425 -metric_L1-I code read misses (w/ prefetches) per instr,4.496827586206897e-05,6.573599999999998e-05,2.161e-05,0.00031314 -metric_L2 demand data read hits per instr,0.005997336965517243,0.010494037999999999,0.00055261,0.01168149 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.005709592482758621,0.012053484,0.00117645,0.01299522 -metric_L2 demand data read MPI,0.0025235795862068957,0.005320572,0.00015868,0.00585237 -metric_L2 demand code MPI,3.6337034482758636e-05,5.3856e-05,1.461e-05,5.64e-05 -metric_LLC data read MPI (demand+prefetch),0.0038248388275862076,0.008921198,0.00056289,0.00936965 -metric_LLC total HITM (per instr),1.4813793103448283e-07,2.539999999999969e-07,1e-08,5.09e-06 -metric_LLC total HIT clean line forwards (per instr),6.56137931034482e-07,6.879999999999998e-07,1.4e-07,1.359e-05 -metric_Average LLC data read miss latency (in clks),260.30812810124144,301.99485137,225.97266152,302.79019506 -metric_Average LLC data read miss latency (in ns),104.97927387331032,112.818485746,87.98986632,145.70201783 -metric_Average LLC data read miss latency for LOCAL requests (in ns),104.60712400048274,112.88802821,87.86734136,119.32024518 -metric_Average LLC data read miss latency for REMOTE requests (in ns),306.41760441641395,323.339279174,173.56502395,325.97925763 -metric_ITLB MPI,1.8496551724137922e-06,2.5359999999999995e-06,7.2e-07,2.62e-06 -metric_DTLB 2MB large page load MPI,7.124413793103446e-06,1.5743999999999997e-05,7.1e-07,1.745e-05 -metric_NUMA %_Reads addressed to local DRAM,99.89597463524149,99.94474062399999,99.47205776,99.94686788 -metric_NUMA %_Reads addressed to remote DRAM,0.10402536475862066,0.1637278779999998,0.05313212,0.52794224 -metric_UPI Data transmit BW (MB/sec) (only data),83.45286023586209,88.6948146,55.2884346,752.0726178 -metric_UPI Transmit utilization_% (includes control),0.6759719544827585,2.9299240119999954,0.14258858,17.19940332 -metric_uncore frequency GHz,1.9873598350344834,2.186620788,1.79734457,2.37919767 -metric_package power (watts),408.35241379310355,409.52,259.24,409.542 -metric_DRAM power (watts),39.94886896551725,49.104,25.964,49.46 -metric_core % cycles in non AVX license,99.99991585993104,100.0,99.9924135,100.0 -metric_core % cycles in AVX2 license,8.414006896551725e-05,0.0,0.0,0.0075865 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),57031.150278973786,89604.14646784001,12780.5494912,90592.5828608 -metric_memory bandwidth write (MB/sec),24147.228011519997,38297.66125824,8226.6798208,39138.6428288 -metric_memory bandwidth total (MB/sec),81178.37829049383,127793.69107455999,21007.229312,129731.2256896 -metric_IO_bandwidth_disk_or_network_writes (MB/sec),0.08504354206896547,0.25744143999999985,0.0,0.5470952 -metric_IO_bandwidth_disk_or_network_reads (MB/sec),0.012728320000000003,0.051834719999999945,0.0,0.069052 -metric_TMAM_Info_CoreIPC,1.598875162344826,2.235378016,0.77623179,2.62272819 -metric_TMAM_Frontend_Bound(%),14.581115361655176,16.708394602,11.06369714,19.1731731 -metric_TMAM_Bad_Speculation(%),14.034905913448277,20.095124218,7.63535982,23.32131174 -metric_TMAM_Backend_bound(%),33.71165109227586,56.71818392,8.11554315,58.76043295 -metric_TMAM_Retiring(%),37.708827876620674,52.523469824,18.2773632,63.97137824 diff --git a/similarity-analyzer/Reference/ICX/500.csv b/similarity-analyzer/Reference/ICX/500.csv deleted file mode 100644 index 90afa61..0000000 --- a/similarity-analyzer/Reference/ICX/500.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.542890891724138,2.622272824,2.45933477,2.65829956 -metric_CPU utilization %,100.42033346275862,107.427091384,92.69828185,114.56716971 -metric_CPU utilization% in kernel mode,0.8467956872413791,1.04432701,0.67391493,1.17525002 -metric_CPI,0.6798162372413792,0.6977673919999999,0.66596276,0.70479556 -metric_kernel_CPI,5.807275616551723,6.416457242,3.79249735,6.59360138 -metric_IPC,1.4713439006896554,1.501357916,1.41885117,1.50158547 -metric_giga_instructions_per_sec,540.9375919972413,579.253979932,500.36183585,613.99306638 -metric_L1D MPI (includes data+rfo w/ prefetches),0.007270351724137931,0.008734285999999999,0.00356232,0.00896472 -metric_L1D demand data read hits per instr,0.31055879344827586,0.31667408399999997,0.28481539,0.3167178 -metric_L1-I code read misses (w/ prefetches) per instr,0.010948194137931036,0.012999133999999999,0.00389228,0.01358398 -metric_L2 demand data read hits per instr,0.004700115517241379,0.005693749999999999,0.00174401,0.00588381 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.0002706503448275862,0.0009334999999999994,3.713e-05,0.00118747 -metric_L2 demand data read MPI,5.325206896551724e-05,0.0002747959999999999,3e-06,0.00036389 -metric_L2 demand code MPI,2.0265862068965522e-05,0.00010104399999999995,2.74e-06,0.00014128 -metric_Average LLC data read miss latency (in clks),249.22079214000004,304.319851556,159.23570223,316.49236821 -metric_UPI Data transmit BW (MB/sec) (only data),48.478985418275855,122.75284947799992,16.3395521,150.65191234 -metric_package power (watts),499.5955917417242,512.892685868,485.22773009,513.32460288 -metric_DRAM power (watts),21.782585463103448,25.254545959999998,20.07554934,27.21728405 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),7242.218588721725,26574.29280160998,415.83911276,34915.06331643 -metric_memory bandwidth write (MB/sec),1990.7858025558623,5516.461658992001,55.07505356,5623.40435027 -metric_memory bandwidth total (MB/sec),9233.00439127724,30809.02164666198,470.91416632,39232.57542236 -metric_ITLB (2nd level) MPI,1.6396206896551727e-05,3.639599999999999e-05,1.106e-05,4.66e-05 -metric_DTLB (2nd level) load MPI,9.18586206896552e-06,4.835399999999998e-05,1.26e-06,6.388e-05 -metric_DTLB (2nd level) 2MB large page load MPI,1.1034482758620691e-07,4.899999999999998e-07,2e-08,5.5e-07 -metric_DTLB (2nd level) store MPI,4.298620689655172e-06,1.0579999999999995e-05,2.87e-06,1.216e-05 -metric_NUMA %_Reads addressed to local DRAM,98.22926948344829,99.657410378,97.07105947,99.81873878 -metric_NUMA %_Reads addressed to remote DRAM,1.7707305165517244,2.8371102819999994,0.18126122,2.92894053 -metric_uncore frequency GHz,0.8659902713793103,1.1175403739999998,0.77606754,1.30103423 -metric_TMA_Frontend_Bound(%),23.08982708068966,27.955522996,20.20914432,29.99589695 -metric_TMA_..Fetch_Latency(%),15.86027873655172,16.239076517999997,14.16817771,16.30174797 -metric_TMA_....ICache_Misses(%),1.93834355,2.2036024899999997,1.26851055,2.24672616 -metric_TMA_....ITLB_Misses(%),2.857640843448276,3.2335756579999995,2.56893984,3.30091902 -metric_TMA_....Branch_Resteers(%),4.863792893103448,5.2279567,3.48749182,5.40731453 -metric_TMA_......Mispredicts_Resteers(%),4.379624208275862,4.746226752,3.03153446,4.94044705 -metric_TMA_......Clears_Resteers(%),0.07863435482758621,0.087354038,0.0600011,0.08740964 -metric_TMA_......Unknown_Branches(%),0.4055343282758621,0.41804379199999997,0.38669789,0.41878136 -metric_TMA_..Fetch_Bandwidth(%),7.229548344137932,11.92072928,4.2079985,14.07607499 -metric_TMA_Bad_Speculation(%),20.544181066206903,28.514276607999996,8.06542402,30.05512468 -metric_TMA_..Branch_Mispredicts(%),20.18319800137931,28.047142257999994,7.90888876,29.59222403 -metric_TMA_..Machine_Clears(%),0.3609830644827586,0.461127618,0.15653526,0.48031225 -metric_TMA_Backend_Bound(%),6.57320848413793,12.12892211199999,3.22512258,17.5482878 -metric_TMA_..Memory_Bound(%),2.392492430689654,5.416270559999996,1.09547491,8.46082062 -metric_TMA_....L1_Bound(%),17.311215444827585,18.003548444,12.09633762,18.04058092 -metric_TMA_......DTLB_Load(%),11.376990778275864,12.480538912,8.17468662,12.68381267 -metric_TMA_......Store_Fwd_Blk(%),0.010751875517241377,0.03724572599999998,0.00456339,0.04185702 -metric_TMA_....L2_Bound(%),1.9044851717241376,2.210129768,0.68356367,2.33384423 -metric_TMA_....L3_Bound(%),0.7621150413793103,3.0639255919999986,0.26892226,3.83318964 -metric_TMA_......Contested_Accesses(%),0.002112661724137931,0.0028562019999999995,0.00083444,0.003126 -metric_TMA_......Data_Sharing(%),0.004423982068965517,0.005649421999999999,0.00138011,0.00653872 -metric_TMA_......L3_Hit_Latency(%),0.17783907310344826,0.6775015679999997,0.02283305,0.8332183 -metric_TMA_......SQ_Full(%),0.13579130551724136,0.19015179399999993,0.05768861,0.22856939 -metric_TMA_......MEM_Bandwidth(%),0.6546436393103446,2.581028779999998,0.0757437,3.4958891 -metric_TMA_......MEM_Latency(%),4.377492249310345,13.773500661999991,1.68238782,15.58695667 -metric_TMA_....Store_Bound(%),1.3670219672413795,5.152926585999998,0.5561879,6.8985576 -metric_TMA_..Core_Bound(%),0.0,0.0,0.0,0.0 -metric_TMA_....Divider(%),0.06122536034482759,0.12383227,0.03009665,0.14975621 -metric_TMA_....Ports_Utilization(%),32.882698594137935,34.371374630000005,26.67152051,34.42164443 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),22.79663569931034,23.539734086000003,17.27265889,23.55544747 -metric_TMA_......Ports_Utilized_2(%),20.318948034827592,20.885645568,17.76670256,20.89970013 -metric_TMA_......Ports_Utilized_3m(%),26.55958206724138,27.170089515999997,25.64558745,28.43606073 -metric_TMA_Retiring(%),49.792783369655176,52.86005521,45.7954394,53.09804144 -metric_TMA_..Light_Operations(%),49.6671349562069,52.75413586799999,45.66722901,52.9693613 -metric_TMA_..Heavy_Operations(%),0.12564841310344824,0.138380242,0.07463775,0.14319488 -metric_TMA_Info_CoreIPC,2.942815412758621,3.0022196560000003,2.83535937,3.00275447 -metric_TMA_Info_System_SMT_2T_Utilization,0.9999763893103448,1.0,0.99965292,1.0 diff --git a/similarity-analyzer/Reference/ICX/502.csv b/similarity-analyzer/Reference/ICX/502.csv deleted file mode 100644 index 9175527..0000000 --- a/similarity-analyzer/Reference/ICX/502.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.668722202413793,2.7108234099999997,2.47200926,2.71586354 -metric_CPU utilization %,100.51847701482758,103.118442422,96.07309188,119.69051168 -metric_CPU utilization% in kernel mode,2.9939461727586205,5.503475203999996,1.44194268,15.52488898 -metric_CPI,1.8303480306896553,2.007605604,1.11723552,2.7312185 -metric_kernel_CPI,3.1556660362068962,4.073086257999999,2.09154042,4.28508863 -metric_IPC,0.5576114744827586,0.6962801459999997,0.36613695,0.89506642 -metric_giga_instructions_per_sec,214.47396878689653,260.2946477319999,171.38535796,326.82428688 -metric_L1D MPI (includes data+rfo w/ prefetches),0.027077869999999997,0.032804561999999995,0.02199063,0.04747932 -metric_L1D demand data read hits per instr,0.2708542427586207,0.27374026399999996,0.26192549,0.27415172 -metric_L1-I code read misses (w/ prefetches) per instr,0.00744565517241379,0.010568573999999997,0.00493391,0.01116721 -metric_L2 demand data read hits per instr,0.007830150344827586,0.009045206,0.00663579,0.00922769 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.019718772758620693,0.02461195799999999,0.01039096,0.03574818 -metric_L2 demand data read MPI,0.004186287586206896,0.004823308,0.00207276,0.00492062 -metric_L2 demand code MPI,0.0003907272413793103,0.0008808859999999999,0.00021839,0.00171462 -metric_Average LLC data read miss latency (in clks),393.8793549875863,437.64586339399995,273.96202709,457.92094365 -metric_UPI Data transmit BW (MB/sec) (only data),1462.5435905996555,4935.544977099999,340.05963542,6016.04541259 -metric_package power (watts),499.21511743896554,501.664131408,495.64977408,513.26276476 -metric_DRAM power (watts),55.67835095862069,57.207347744,47.05942478,57.6966633 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),222747.64668707584,234669.61856630002,143637.18346546,238744.81083245 -metric_memory bandwidth write (MB/sec),40765.79680028483,49668.486826254,34101.4100449,70933.59174451 -metric_memory bandwidth total (MB/sec),263513.4434873613,279585.11694647,182735.31231239,286557.20127066 -metric_ITLB (2nd level) MPI,5.449793103448276e-05,0.00017327199999999995,1.168e-05,0.00021823 -metric_DTLB (2nd level) load MPI,0.00035149,0.0004918459999999999,9.907e-05,0.00099817 -metric_DTLB (2nd level) 2MB large page load MPI,6.138275862068966e-06,1.269599999999998e-05,3.59e-06,3.448e-05 -metric_DTLB (2nd level) store MPI,7.758827586206896e-05,0.00016362199999999972,3.313e-05,0.00052171 -metric_NUMA %_Reads addressed to local DRAM,99.59885286034482,99.952088842,97.88993525,99.95403286 -metric_NUMA %_Reads addressed to remote DRAM,0.40114713965517246,1.7387591279999997,0.04596714,2.11006475 -metric_uncore frequency GHz,1.7019393358620691,1.72747856,1.6364383,1.73285107 -metric_TMA_Frontend_Bound(%),8.585568693448277,18.951929947999982,3.86604286,30.01864403 -metric_TMA_..Fetch_Latency(%),10.87636862275862,14.059335825999995,9.16807569,18.70750935 -metric_TMA_....ICache_Misses(%),1.6005562803448274,2.3717567599999994,1.17891719,2.96489479 -metric_TMA_....ITLB_Misses(%),2.1755321468965514,3.7266776059999986,1.30461399,9.34890332 -metric_TMA_....Branch_Resteers(%),1.6199478424137932,2.6312199119999975,1.22473307,5.29034707 -metric_TMA_......Mispredicts_Resteers(%),1.3867471648275866,2.2093413939999977,1.08353506,4.39852189 -metric_TMA_......Clears_Resteers(%),0.06653284586206897,0.11583011999999981,0.04391297,0.29884853 -metric_TMA_......Unknown_Branches(%),0.16666783068965516,0.39405913999999975,0.08073459,0.84187842 -metric_TMA_..Fetch_Bandwidth(%),0.8982559206896552,6.058847501999993,0.0,11.31113468 -metric_TMA_Bad_Speculation(%),36.93011930448276,57.85902062199999,5.52227861,58.60663829 -metric_TMA_..Branch_Mispredicts(%),34.93253702793103,55.618485694,5.28463795,56.43408684 -metric_TMA_..Machine_Clears(%),1.9975822779310344,4.519793773999993,0.14000279,10.77467933 -metric_TMA_Backend_Bound(%),43.161949511034464,58.63655383399999,25.82202741,61.63611756 -metric_TMA_..Memory_Bound(%),34.84492753758621,48.31453224799999,15.66873109,50.63150544 -metric_TMA_....L1_Bound(%),7.194569158620688,9.200464471999997,6.34310613,12.97030359 -metric_TMA_......DTLB_Load(%),5.925085031034483,6.883561018,3.88887333,9.8689404 -metric_TMA_......Store_Fwd_Blk(%),0.26296508896551724,0.373163138,0.1752384,0.42056793 -metric_TMA_....L2_Bound(%),1.7217107231034485,2.1708970599999997,0.14433186,2.36256633 -metric_TMA_....L3_Bound(%),5.413433638965517,5.799426754,4.56220188,5.89915952 -metric_TMA_......Contested_Accesses(%),0.02845052655172414,0.06996494599999997,0.00482791,0.14619193 -metric_TMA_......Data_Sharing(%),0.02604648827586207,0.04833127199999998,0.00974347,0.07559638 -metric_TMA_......L3_Hit_Latency(%),3.4066045358620687,3.915886632,2.35309752,6.34434381 -metric_TMA_......SQ_Full(%),1.244770408965517,1.841267276,0.73991051,2.10920923 -metric_TMA_......MEM_Bandwidth(%),30.542444001034486,36.07468290999999,13.23755847,37.14292175 -metric_TMA_......MEM_Latency(%),45.32318453551724,48.595298434,35.1794058,48.98968563 -metric_TMA_....Store_Bound(%),4.1001735551724146,6.646127149999994,3.08095423,15.31000799 -metric_TMA_..Core_Bound(%),0.13471021,0.0,0.0,3.90659609 -metric_TMA_....Divider(%),0.11673475758620691,0.18485510599999985,0.05909057,0.25240928 -metric_TMA_....Ports_Utilization(%),10.028932712413797,13.20250673399999,8.71180993,20.01370859 -metric_TMA_......Ports_Utilized_0(%),0.10380498344827585,0.0,0.0,3.01034452 -metric_TMA_......Ports_Utilized_1(%),9.057601893793102,11.462115003999996,7.03897834,15.96118736 -metric_TMA_......Ports_Utilized_2(%),7.221351950344827,9.383140897999997,5.36198463,12.86295295 -metric_TMA_......Ports_Utilized_3m(%),9.226579052413795,11.688122269999997,6.68121038,14.81639328 -metric_TMA_Retiring(%),11.322362492068965,22.775699255999985,6.43541743,31.69009009 -metric_TMA_..Light_Operations(%),11.14335044827586,22.535652079999988,5.61507116,31.428645 -metric_TMA_..Heavy_Operations(%),0.17901204206896554,0.36044751999999963,0.0609152,0.82034627 -metric_TMA_Info_CoreIPC,1.1162848131034482,1.4087051979999994,0.73714361,1.77489329 -metric_TMA_Info_System_SMT_2T_Utilization,0.9999313403448276,1.0,0.99927236,1.0 diff --git a/similarity-analyzer/Reference/ICX/505.csv b/similarity-analyzer/Reference/ICX/505.csv deleted file mode 100644 index 53612ac..0000000 --- a/similarity-analyzer/Reference/ICX/505.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.6505676337931035,2.7587018719999996,2.53255838,2.77756044 -metric_CPU utilization %,101.01370074068967,105.70548567,93.71063481,132.51251356 -metric_CPU utilization% in kernel mode,0.7972008482758622,0.9108420979999999,0.70342607,1.10121194 -metric_CPI,3.597247607931035,4.394915436,2.82538187,4.57960252 -metric_kernel_CPI,6.2392941837931035,6.640337792,5.00544661,6.85359555 -metric_IPC,0.28298390827586206,0.346339278,0.21835956,0.35393446 -metric_giga_instructions_per_sec,108.71466328758622,132.19229259399998,87.77336746,135.13743091 -metric_L1D MPI (includes data+rfo w/ prefetches),0.08294643068965515,0.093127398,0.06764381,0.09475286 -metric_L1D demand data read hits per instr,0.23085582896551726,0.245875084,0.21027135,0.24816944 -metric_L1-I code read misses (w/ prefetches) per instr,0.00011811724137931033,0.0001808719999999998,9.483e-05,0.00022091 -metric_L2 demand data read hits per instr,0.028988902413793104,0.035376444,0.01805994,0.03744956 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.05509420999999999,0.063665364,0.04190021,0.06705072 -metric_L2 demand data read MPI,0.016147665862068967,0.020968574,0.01250455,0.02159495 -metric_L2 demand code MPI,0.00011064758620689655,0.00011912199999999999,9.097e-05,0.00013012 -metric_Average LLC data read miss latency (in clks),350.38352233896563,420.25594821999994,281.22404224,440.26723705 -metric_UPI Data transmit BW (MB/sec) (only data),191.12897072379312,213.536247724,156.00665996,216.36806821 -metric_package power (watts),498.9649652499999,500.173882238,491.47616991,513.5827319 -metric_DRAM power (watts),57.20600288827586,59.263945109999995,52.68957552,59.55651979 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),265095.406403629,274315.583697608,249968.43917262,278050.96359036 -metric_memory bandwidth write (MB/sec),29381.53983781759,41114.32655309799,13305.17683447,44410.8994092 -metric_memory bandwidth total (MB/sec),294476.94624144793,306300.556979606,264722.80091633,308550.12316725 -metric_ITLB (2nd level) MPI,6.410344827586208e-07,1.5719999999999985e-06,3.1e-07,1.86e-06 -metric_DTLB (2nd level) load MPI,2.3603448275862067e-06,3.1599999999999994e-06,1.47e-06,3.9e-06 -metric_DTLB (2nd level) 2MB large page load MPI,2.5344827586206897e-07,3.5e-07,1.5e-07,4e-07 -metric_DTLB (2nd level) store MPI,7.822758620689656e-06,1.384399999999999e-05,5.47e-06,1.583e-05 -metric_NUMA %_Reads addressed to local DRAM,99.96502870517243,99.970788244,99.95767267,99.97119967 -metric_NUMA %_Reads addressed to remote DRAM,0.034971294827586215,0.039679980000000004,0.02880033,0.04232733 -metric_uncore frequency GHz,1.6952316272413797,1.745953558,1.62926038,1.7661492 -metric_TMA_Frontend_Bound(%),6.909178661379311,18.440088573999997,2.69271669,20.73438656 -metric_TMA_..Fetch_Latency(%),10.634136284827589,13.957845171999999,6.40336274,14.3157189 -metric_TMA_....ICache_Misses(%),0.3320138589655172,0.36696107199999994,0.273288,0.37910947 -metric_TMA_....ITLB_Misses(%),0.005665464482758621,0.007839111999999999,0.00422118,0.00912692 -metric_TMA_....Branch_Resteers(%),4.902660897241379,7.231401099999999,2.38235085,7.41631239 -metric_TMA_......Mispredicts_Resteers(%),4.776338164137932,7.065934482,2.31997156,7.23324339 -metric_TMA_......Clears_Resteers(%),0.013574346206896553,0.018694138,0.00816324,0.01988749 -metric_TMA_......Unknown_Branches(%),0.11274838793103449,0.15325650999999998,0.05421605,0.16514877 -metric_TMA_..Fetch_Bandwidth(%),0.6992681279310344,4.951275695999998,0.0,6.72340898 -metric_TMA_Bad_Speculation(%),48.22690852517242,69.16589265799999,24.44127026,73.60806537 -metric_TMA_..Branch_Mispredicts(%),48.08859204551724,68.9774085,24.37447495,73.41337362 -metric_TMA_..Machine_Clears(%),0.13831647896551721,0.188915702,0.06679531,0.19469176 -metric_TMA_Backend_Bound(%),39.86258292689655,58.42796880399999,18.43878524,61.78652505 -metric_TMA_..Memory_Bound(%),31.09325440172414,48.93333968599998,12.50165049,53.45304658 -metric_TMA_....L1_Bound(%),2.551408227241379,4.125954935999999,1.2438276,4.21249967 -metric_TMA_......DTLB_Load(%),1.7101502441379308,2.0716430260000003,1.23367132,2.14661634 -metric_TMA_......Store_Fwd_Blk(%),0.09761729655172414,0.19259001399999998,0.0211335,0.34077456 -metric_TMA_....L2_Bound(%),0.27563680517241385,0.42762199,0.07765746,0.62943495 -metric_TMA_....L3_Bound(%),4.33527266862069,5.084143808,3.04542415,5.1387311 -metric_TMA_......Contested_Accesses(%),0.0010624189655172415,0.0013828039999999996,0.00059426,0.0017472 -metric_TMA_......Data_Sharing(%),0.0021323586206896555,0.0026338259999999997,0.00154119,0.00269703 -metric_TMA_......L3_Hit_Latency(%),13.804415213448275,16.110765256,6.66674729,16.86034053 -metric_TMA_......SQ_Full(%),5.746837990344828,7.2978420559999995,3.35180605,8.07614195 -metric_TMA_......MEM_Bandwidth(%),55.24995918344828,64.33875130999999,42.84893133,65.06153742 -metric_TMA_......MEM_Latency(%),23.334137649655176,24.257483082,21.21271285,24.33349113 -metric_TMA_....Store_Bound(%),2.714802168965517,4.465818585999999,0.94244553,4.72990458 -metric_TMA_..Core_Bound(%),0.0,0.0,0.0,0.0 -metric_TMA_....Divider(%),0.07134564724137932,0.101767134,0.03375385,0.11868516 -metric_TMA_....Ports_Utilization(%),10.470360111724139,13.420198472,7.06467606,14.54776838 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),10.148393680689653,12.912117989999999,6.89787841,13.74551761 -metric_TMA_......Ports_Utilized_2(%),6.289572354482758,8.362299027999999,3.95016706,8.63338338 -metric_TMA_......Ports_Utilized_3m(%),6.926368271034483,8.168634522,5.12866888,8.32775963 -metric_TMA_Retiring(%),5.001329886896552,9.601407681999998,3.45735116,9.98777431 -metric_TMA_..Light_Operations(%),4.9937719662068965,9.588493016,3.4522654,9.97371296 -metric_TMA_..Heavy_Operations(%),0.007557920000000001,0.012914665999999995,0.00508576,0.01406136 -metric_TMA_Info_CoreIPC,0.5656602868965518,0.688327922,0.42503653,0.7128717 -metric_TMA_Info_System_SMT_2T_Utilization,1.0,1.0,1.0,1.0 diff --git a/similarity-analyzer/Reference/ICX/520.csv b/similarity-analyzer/Reference/ICX/520.csv deleted file mode 100644 index 7d81411..0000000 --- a/similarity-analyzer/Reference/ICX/520.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.7307280310344826,2.7458529140000003,2.71108265,2.74939022 -metric_CPU utilization %,102.68436176241379,108.509632512,91.58878724,179.59726678 -metric_CPU utilization% in kernel mode,0.6524095179310345,0.6857920319999999,0.59097509,1.07270326 -metric_CPI,2.531131812068966,2.551631758,2.5140949,2.59269423 -metric_kernel_CPI,5.154424715172414,5.312354066,3.88585633,5.31687591 -metric_IPC,0.39509485310344833,0.39757438,0.38569917,0.39775746 -metric_giga_instructions_per_sec,159.51617441896548,168.99152504799997,142.69370524,278.32441504 -metric_L1D MPI (includes data+rfo w/ prefetches),0.033347321724137936,0.033405884,0.0332248,0.03341298 -metric_L1D demand data read hits per instr,0.30550755000000007,0.305676418,0.30497323,0.30571021 -metric_L1-I code read misses (w/ prefetches) per instr,0.00045857620689655185,0.0005066859999999999,0.00044122,0.00053959 -metric_L2 demand data read hits per instr,0.008608035172413795,0.0086492,0.00856135,0.00866146 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.01982379793103449,0.019946542,0.01969248,0.01999737 -metric_L2 demand data read MPI,0.006964207586206896,0.007006198,0.00687732,0.00721874 -metric_L2 demand code MPI,0.00031250862068965524,0.000316688,0.00030715,0.00031747 -metric_Average LLC data read miss latency (in clks),335.40836929413797,337.357185512,332.71505343,337.50444492 -metric_UPI Data transmit BW (MB/sec) (only data),95.42479135965516,97.310638192,91.43834301,137.25272488 -metric_package power (watts),500.8236053748276,510.7349597,491.74009013,548.2263034 -metric_DRAM power (watts),53.352907028275865,54.270730836,52.3261911,58.47654966 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),175820.35484821652,179173.020171114,172539.99945527,192856.38977557 -metric_memory bandwidth write (MB/sec),51280.28186953172,52092.612213514,50365.91329459,56154.86659519 -metric_memory bandwidth total (MB/sec),227100.63671774685,231265.63238462797,222905.91274986,249011.25637076 -metric_ITLB (2nd level) MPI,7.023206896551724e-05,7.1224e-05,6.882e-05,7.202e-05 -metric_DTLB (2nd level) load MPI,0.0012518162068965518,0.0012636980000000002,0.00121002,0.00133523 -metric_DTLB (2nd level) 2MB large page load MPI,1.844068965517241e-05,1.8924e-05,1.808e-05,1.943e-05 -metric_DTLB (2nd level) store MPI,0.0003632048275862069,0.000369512,0.00034434,0.00036995 -metric_NUMA %_Reads addressed to local DRAM,99.96440960344826,99.96672977,99.95542243,99.96725344 -metric_NUMA %_Reads addressed to remote DRAM,0.035590396551724135,0.036850884,0.03274656,0.04457757 -metric_uncore frequency GHz,1.7319123768965519,1.77850874,1.7028094,1.90841591 -metric_TMA_Frontend_Bound(%),13.975593126896554,14.082554564000002,13.84550363,14.1118954 -metric_TMA_..Fetch_Latency(%),9.164456695862068,9.208753248,9.12327158,9.21511584 -metric_TMA_....ICache_Misses(%),0.37116721551724136,0.37922329199999993,0.36263112,0.38019219 -metric_TMA_....ITLB_Misses(%),4.853234642413793,4.945272078,4.67172059,5.44556104 -metric_TMA_....Branch_Resteers(%),1.9252575282758624,1.934107818,1.91143992,1.93565196 -metric_TMA_......Mispredicts_Resteers(%),1.8378709017241377,1.8464564759999997,1.82365251,1.84774695 -metric_TMA_......Clears_Resteers(%),0.012595547931034482,0.012693266,0.01246691,0.01269535 -metric_TMA_......Unknown_Branches(%),0.07479107862068966,0.07600536,0.07378743,0.07611656 -metric_TMA_..Fetch_Bandwidth(%),4.811136430344827,4.923308448,4.71955261,4.93961012 -metric_TMA_Bad_Speculation(%),9.66575868862069,10.209488482,8.86602647,10.24190348 -metric_TMA_..Branch_Mispredicts(%),9.599972867931035,10.140096283999998,8.80502054,10.17274197 -metric_TMA_..Machine_Clears(%),0.06578582068965518,0.069218138,0.06100592,0.06948307 -metric_TMA_Backend_Bound(%),61.26487093275862,61.65935134,60.65097045,62.28820508 -metric_TMA_..Memory_Bound(%),48.1803803113793,48.51576498199999,47.68856791,49.3760065 -metric_TMA_....L1_Bound(%),1.7584283731034482,1.780644638,1.72503529,1.82163915 -metric_TMA_......DTLB_Load(%),5.102494588965518,5.447052168,4.702905,5.497305 -metric_TMA_......Store_Fwd_Blk(%),0.0012117110344827585,0.001300894,0.00118365,0.0013484 -metric_TMA_....L2_Bound(%),0.45103647103448274,0.45508388,0.44388056,0.45842788 -metric_TMA_....L3_Bound(%),5.98339318862069,6.013771248,5.92222563,6.10025406 -metric_TMA_......Contested_Accesses(%),0.0005927324137931033,0.00079038,0.00043338,0.00084125 -metric_TMA_......Data_Sharing(%),0.001862514482758621,0.002404974,0.00144132,0.00255364 -metric_TMA_......L3_Hit_Latency(%),3.4175575886206895,3.4458402579999996,3.32120746,3.4464033 -metric_TMA_......SQ_Full(%),0.05413051275862069,0.05560140599999999,0.04956643,0.05597805 -metric_TMA_......MEM_Bandwidth(%),21.75205649586207,21.830844182,21.41846617,21.84911648 -metric_TMA_......MEM_Latency(%),58.97937940344828,59.1235348,58.80365823,59.48694834 -metric_TMA_....Store_Bound(%),10.64102896586207,10.838106419999999,10.14949775,10.87884375 -metric_TMA_..Core_Bound(%),0.0,0.0,0.0,0.0 -metric_TMA_....Divider(%),0.005735969999999998,0.005781396,0.0057065,0.00579553 -metric_TMA_....Ports_Utilization(%),7.961089758620688,8.023956576,7.80871168,8.02549831 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),7.107654471724138,7.1563053299999995,6.98717925,7.16052078 -metric_TMA_......Ports_Utilized_2(%),5.671447724827585,5.709653656,5.54091479,5.7131137 -metric_TMA_......Ports_Utilized_3m(%),9.032555067931035,9.088355034,8.81429554,9.09334917 -metric_TMA_Retiring(%),15.09377725172414,15.268741125999998,14.79247789,15.27420008 -metric_TMA_..Light_Operations(%),15.061538235172414,15.236322570000002,14.76103469,15.24166518 -metric_TMA_..Heavy_Operations(%),0.032239015172413794,0.033292063999999996,0.03111771,0.03389206 -metric_TMA_Info_CoreIPC,0.7898811237931035,0.794798058,0.77061545,0.79560377 -metric_TMA_Info_System_SMT_2T_Utilization,0.999991782413793,1.0,0.99989965,1.0 diff --git a/similarity-analyzer/Reference/ICX/523.csv b/similarity-analyzer/Reference/ICX/523.csv deleted file mode 100644 index 044ec7d..0000000 --- a/similarity-analyzer/Reference/ICX/523.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.499318464210526,2.509483949,2.48543179,2.51577098 -metric_CPU utilization %,99.79508572421054,103.300557821,94.55074574,103.81715846 -metric_CPU utilization% in kernel mode,0.778115832631579,0.8728790820000001,0.71373423,0.88587924 -metric_CPI,0.8307873126315788,0.8359446189999999,0.82480908,0.83694532 -metric_kernel_CPI,4.123906274210526,4.629860513000001,3.40523737,4.65685658 -metric_IPC,1.2037004078947369,1.211016851,1.19482118,1.21240178 -metric_giga_instructions_per_sec,432.3215868063158,447.19847220099996,413.68198865,451.63382503 -metric_L1D MPI (includes data+rfo w/ prefetches),0.05245850631578947,0.053026624999999994,0.05108871,0.05314799 -metric_L1D demand data read hits per instr,0.3136146899999999,0.314217724,0.31235214,0.31426987 -metric_L1-I code read misses (w/ prefetches) per instr,0.001785235789473684,0.0023235139999999987,0.0016348,0.00281432 -metric_L2 demand data read hits per instr,0.020609007894736842,0.020747918999999997,0.02012062,0.02075727 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.003091707894736842,0.003160520999999999,0.00298789,0.00336996 -metric_L2 demand data read MPI,0.00015214842105263157,0.00017227399999999996,0.00013417,0.00019211 -metric_L2 demand code MPI,0.00013615736842105265,0.00014222299999999997,0.00013044,0.0001553 -metric_Average LLC data read miss latency (in clks),239.42878369842106,240.67747347699998,237.70435,241.0002454 -metric_UPI Data transmit BW (MB/sec) (only data),173.5051935942105,270.7438128489998,136.3077714,345.87972694 -metric_package power (watts),499.34910622736834,506.40099623000003,492.0061839,513.57651389 -metric_DRAM power (watts),37.43711668894737,37.988596933000004,36.61088127,38.45541139 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),74143.77830716631,76578.72267665701,71916.88572732,79033.89074625 -metric_memory bandwidth write (MB/sec),42457.945556258426,43852.337129701,40793.31035109,44523.72521299 -metric_memory bandwidth total (MB/sec),116601.72386342681,118658.96578378801,113053.62407419,119827.20109734 -metric_ITLB (2nd level) MPI,3.084052631578948e-05,3.3359e-05,2.976e-05,3.461e-05 -metric_DTLB (2nd level) load MPI,1.180736842105263e-05,1.3664999999999995e-05,1.037e-05,1.488e-05 -metric_DTLB (2nd level) 2MB large page load MPI,6.136842105263157e-07,7.369999999999997e-07,5.4e-07,8.9e-07 -metric_DTLB (2nd level) store MPI,3.3458947368421054e-05,3.5345e-05,3.042e-05,3.566e-05 -metric_NUMA %_Reads addressed to local DRAM,99.46942265368419,99.520705382,99.35869869,99.54882149 -metric_NUMA %_Reads addressed to remote DRAM,0.5305773463157896,0.6010560549999999,0.45117851,0.64130131 -metric_uncore frequency GHz,1.598508285263158,1.6228055129999999,1.56763873,1.63215672 -metric_TMA_Frontend_Bound(%),28.948844671052633,29.63979774,28.09688925,29.76670638 -metric_TMA_..Fetch_Latency(%),18.322947394736847,18.437029991,18.23168676,18.49782986 -metric_TMA_....ICache_Misses(%),0.9184055910526316,1.036633295,0.89250587,1.08141041 -metric_TMA_....ITLB_Misses(%),3.9507974984210525,4.0667786139999995,3.88789833,4.18635796 -metric_TMA_....Branch_Resteers(%),1.5874943815789475,1.6292474339999998,1.53716586,1.68572787 -metric_TMA_......Mispredicts_Resteers(%),1.2293217842105264,1.257827635,1.20761969,1.29415753 -metric_TMA_......Clears_Resteers(%),0.02798584631578947,0.029312375999999994,0.02733286,0.03082533 -metric_TMA_......Unknown_Branches(%),0.33018675263157893,0.354742299,0.30179081,0.36074502 -metric_TMA_..Fetch_Bandwidth(%),10.625897277368423,11.393397352000001,9.77415844,11.45157256 -metric_TMA_Bad_Speculation(%),4.121166046842105,8.997315599999999,1.32160084,9.83289774 -metric_TMA_..Branch_Mispredicts(%),4.029071357894736,8.792386076999998,1.29191106,9.60413835 -metric_TMA_..Machine_Clears(%),0.09209468842105265,0.20492951399999995,0.02968977,0.22875939 -metric_TMA_Backend_Bound(%),31.071429339999998,32.690058586,27.10742746,32.96875039 -metric_TMA_..Memory_Bound(%),15.889553351578945,16.763071565,13.93719021,16.90354829 -metric_TMA_....L1_Bound(%),18.557460835263157,18.646239935,18.3675751,18.69815504 -metric_TMA_......DTLB_Load(%),9.854130233157894,10.073409874,9.49221863,10.20166288 -metric_TMA_......Store_Fwd_Blk(%),0.452594994736842,0.46040966400000005,0.43260301,0.4612602 -metric_TMA_....L2_Bound(%),10.538496684736842,10.857804759,10.14588185,10.94513544 -metric_TMA_....L3_Bound(%),0.9495522426315791,1.0507178449999999,0.85641282,1.07985764 -metric_TMA_......Contested_Accesses(%),0.0035473157894736836,0.004998286999999997,0.00289005,0.00607349 -metric_TMA_......Data_Sharing(%),0.004410422105263157,0.005510369999999999,0.00358132,0.00582186 -metric_TMA_......L3_Hit_Latency(%),0.8846972984210526,0.948886217,0.79388984,0.96475472 -metric_TMA_......SQ_Full(%),0.3537605010526316,0.378839263,0.32216732,0.38193817 -metric_TMA_......MEM_Bandwidth(%),2.1597949236842107,2.639450936,1.9670796,2.84048831 -metric_TMA_......MEM_Latency(%),13.538248633684208,14.074509026999998,12.93828235,15.2470623 -metric_TMA_....Store_Bound(%),1.5689664915789472,1.611220796,1.51165253,1.61138996 -metric_TMA_..Core_Bound(%),0.0,0.0,0.0,0.0 -metric_TMA_....Divider(%),0.04397925578947368,0.1211737639999998,0.0218285,0.2034671 -metric_TMA_....Ports_Utilization(%),30.902624203684212,31.156668977,30.40887306,31.28265608 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),24.21951759052632,24.377713261,23.97753129,24.40723984 -metric_TMA_......Ports_Utilized_2(%),18.674696641578944,18.777874442999998,18.54258393,18.79108512 -metric_TMA_......Ports_Utilized_3m(%),15.657236686842102,15.787832647,15.3424029,15.83967451 -metric_TMA_Retiring(%),35.85855994105263,36.57133211,34.79683163,36.68317088 -metric_TMA_..Light_Operations(%),35.7483179468421,36.459492955,34.68009089,36.57383953 -metric_TMA_..Heavy_Operations(%),0.11024199578947369,0.11647128000000001,0.10500145,0.11674074 -metric_TMA_Info_CoreIPC,2.4079186289473684,2.422686035,2.39350649,2.4227633 -metric_TMA_Info_System_SMT_2T_Utilization,1.0,1.0,1.0,1.0 diff --git a/similarity-analyzer/Reference/ICX/525.csv b/similarity-analyzer/Reference/ICX/525.csv deleted file mode 100644 index 8ff6ae5..0000000 --- a/similarity-analyzer/Reference/ICX/525.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.614511372631579,2.6219230309999997,2.59516703,2.62319798 -metric_CPU utilization %,99.93609157894738,105.285593362,93.87928496,106.46155822 -metric_CPU utilization% in kernel mode,0.941377374736842,1.032149655,0.86432686,1.03440141 -metric_CPI,0.6043507431578948,0.607983962,0.60074621,0.60917144 -metric_kernel_CPI,6.174004474736844,6.495751466000001,5.18127559,6.55681796 -metric_IPC,1.6546906263157892,1.663844876,1.64157399,1.66459643 -metric_giga_instructions_per_sec,622.5786666684211,657.778004558,585.67096159,663.32669156 -metric_L1D MPI (includes data+rfo w/ prefetches),0.0038830710526315793,0.0042253379999999995,0.00348548,0.00438876 -metric_L1D demand data read hits per instr,0.2620448784210526,0.263631004,0.26068342,0.26405998 -metric_L1-I code read misses (w/ prefetches) per instr,0.0042327289473684205,0.004834203,0.00390026,0.00508956 -metric_L2 demand data read hits per instr,0.0015232936842105263,0.0018991469999999997,0.00132479,0.00202485 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.0007237752631578947,0.000847052,0.00063435,0.00085706 -metric_L2 demand data read MPI,0.00010866210526315792,0.00013661999999999998,8.563e-05,0.00013887 -metric_L2 demand code MPI,2.3760526315789473e-05,2.6518e-05,2.084e-05,2.704e-05 -metric_Average LLC data read miss latency (in clks),265.10588777526317,269.740347056,262.54020135,270.01600901 -metric_UPI Data transmit BW (MB/sec) (only data),443.3189989515789,604.2595148749999,376.74918072,654.67826498 -metric_package power (watts),499.30518861736846,500.368160168,497.91983525,513.36080666 -metric_DRAM power (watts),24.581636300526316,25.654321337,24.11409647,26.46541895 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),21763.178312639473,26097.357735562993,19719.60096201,27029.81311466 -metric_memory bandwidth write (MB/sec),5250.730899602632,7079.224039877999,4464.62482421,7573.60978599 -metric_memory bandwidth total (MB/sec),27013.909212243154,33176.581775441,24490.08686433,34603.42290065 -metric_ITLB (2nd level) MPI,1.4886315789473683e-05,1.5398e-05,1.406e-05,1.547e-05 -metric_DTLB (2nd level) load MPI,1.4647368421052633e-06,1.5929999999999998e-06,1.36e-06,1.62e-06 -metric_DTLB (2nd level) 2MB large page load MPI,1.1526315789473686e-07,1.4099999999999998e-07,1e-07,1.5e-07 -metric_DTLB (2nd level) store MPI,1.869473684210526e-06,2.11e-06,1.56e-06,2.11e-06 -metric_NUMA %_Reads addressed to local DRAM,98.39091186578946,98.577681359,97.93449928,98.67643025 -metric_NUMA %_Reads addressed to remote DRAM,1.6090881342105263,1.9371011489999996,1.32356975,2.06550072 -metric_uncore frequency GHz,0.7985593778947369,0.800276023,0.79628345,0.82075015 -metric_TMA_Frontend_Bound(%),17.604442339473685,18.686344378999998,16.9403699,19.87202993 -metric_TMA_..Fetch_Latency(%),11.556019035263159,11.63012982,11.35979063,11.64077457 -metric_TMA_....ICache_Misses(%),0.6849063421052634,0.7058143870000001,0.6569157,0.70931554 -metric_TMA_....ITLB_Misses(%),0.4830675278947369,0.503023581,0.45606504,0.50450472 -metric_TMA_....Branch_Resteers(%),2.16305935631579,2.381556409,1.55435911,2.39991847 -metric_TMA_......Mispredicts_Resteers(%),1.892370962631579,2.080681749,1.35003506,2.09923623 -metric_TMA_......Clears_Resteers(%),0.053340651052631576,0.05708405,0.04196666,0.05710142 -metric_TMA_......Unknown_Branches(%),0.21734774421052633,0.245435133,0.16105826,0.24659139 -metric_TMA_..Fetch_Bandwidth(%),6.048423306315789,7.140906958999997,5.35362044,8.24308286 -metric_TMA_Bad_Speculation(%),10.239772833157895,12.974243122999999,3.00751471,13.40290109 -metric_TMA_..Branch_Mispredicts(%),9.959800698421054,12.641782647,2.91863282,13.05620985 -metric_TMA_..Machine_Clears(%),0.2799721347368421,0.344995118,0.08888189,0.34669124 -metric_TMA_Backend_Bound(%),8.675678011578947,9.177737716999996,8.15699808,10.63706969 -metric_TMA_..Memory_Bound(%),1.8998665526315794,2.0205951729999994,1.78614059,2.32798498 -metric_TMA_....L1_Bound(%),9.29944079473684,9.435920494,9.11881353,9.43788343 -metric_TMA_......DTLB_Load(%),0.6041986968421051,0.7198271780000001,0.5003597,0.72507011 -metric_TMA_......Store_Fwd_Blk(%),0.5425776878947368,0.595901214,0.48276439,0.6144903 -metric_TMA_....L2_Bound(%),0.3988014021052632,0.427756226,0.34005008,0.43155401 -metric_TMA_....L3_Bound(%),0.8436761357894738,0.8980238519999999,0.75953802,0.92398995 -metric_TMA_......Contested_Accesses(%),0.0011675210526315791,0.0018624019999999982,0.00055258,0.00259871 -metric_TMA_......Data_Sharing(%),0.002560914736842105,0.00391123,0.00160419,0.00394174 -metric_TMA_......L3_Hit_Latency(%),0.41934530368421047,0.5256358649999999,0.33197021,0.5427954 -metric_TMA_......SQ_Full(%),0.18615972578947368,0.24927978499999998,0.15936838,0.25991099 -metric_TMA_......MEM_Bandwidth(%),2.50889254,2.7365203379999996,2.22620022,2.80423884 -metric_TMA_......MEM_Latency(%),8.343931622631578,9.064821177999999,7.5212437,9.09604882 -metric_TMA_....Store_Bound(%),0.8195709184210527,1.1313420639999998,0.71449247,1.18916863 -metric_TMA_..Core_Bound(%),0.0,0.0,0.0,0.0 -metric_TMA_....Divider(%),0.08459899368421052,0.10682484,0.06760873,0.10702203 -metric_TMA_....Ports_Utilization(%),42.86399736789474,43.77003842,41.9651082,44.36272865 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),25.288823612105265,25.729015540000002,24.93188905,25.80817927 -metric_TMA_......Ports_Utilized_2(%),27.697519034210533,27.903718509999997,27.59066143,27.90832696 -metric_TMA_......Ports_Utilized_3m(%),27.706709121052636,27.984182883,27.46359377,28.01243733 -metric_TMA_Retiring(%),63.48010681421054,65.18975661099999,60.83685468,66.48338566 -metric_TMA_..Light_Operations(%),63.42087872736841,65.12559704899999,60.78145816,66.41530784 -metric_TMA_..Heavy_Operations(%),0.059228086842105274,0.066761768,0.05539652,0.06807782 -metric_TMA_Info_CoreIPC,3.3097498015789473,3.322329756,3.28864062,3.33157632 -metric_TMA_Info_System_SMT_2T_Utilization,1.0,1.0,1.0,1.0 diff --git a/similarity-analyzer/Reference/ICX/531.csv b/similarity-analyzer/Reference/ICX/531.csv deleted file mode 100644 index 582c01c..0000000 --- a/similarity-analyzer/Reference/ICX/531.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.619788066,2.699960064,2.52234119,2.70245078 -metric_CPU utilization %,101.981329,103.2039954,96.24580685,161.0322286 -metric_CPU utilization% in kernel mode,0.500411142,0.606635732,0.36668951,0.65558862 -metric_CPI,0.884970842,0.914910534,0.85043594,0.91600361 -metric_kernel_CPI,3.78388907,4.833759366,2.46068023,4.87443567 -metric_IPC,1.130424786,1.160722562,1.09169875,1.17586751 -metric_giga_instructions_per_sec,434.8433854,451.1781813,404.5198519,691.9408701 -metric_L1D MPI (includes data+rfo w/ prefetches),0.003659638,0.004180506,0.00315842,0.00425681 -metric_L1D demand data read hits per instr,0.229378234,0.23396521,0.22499879,0.23490235 -metric_L1-I code read misses (w/ prefetches) per instr,0.003440448,0.00415588,0.00280559,0.00426494 -metric_L2 demand data read hits per instr,0.001786279,0.001948032,0.00155367,0.00197017 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.000688046,0.001219714,0.00054467,0.00153783 -metric_L2 demand data read MPI,0.000312827,0.00033876,0.00027986,0.00035026 -metric_L2 demand code MPI,4.47E-06,9.21E-06,2.87E-06,1.22E-05 -metric_Average LLC data read miss latency (in clks),212.3373303,242.7975118,181.458292,243.3349392 -metric_UPI Data transmit BW (MB/sec) (only data),41.49414711,46.55775472,35.59477899,68.00858104 -metric_package power (watts),499.4901009,506.2161322,498.1189027,513.7276521 -metric_DRAM power (watts),24.05927293,27.28784278,23.07705892,28.15135454 -metric_core % cycles in non AVX license,100,100,100,100 -metric_core % cycles in AVX2 license,0,0,0,0 -metric_core % cycles in AVX-512 license,0,0,0,0 -metric_memory bandwidth read (MB/sec),16598.67814,26852.55155,12895.9037,30319.43954 -metric_memory bandwidth write (MB/sec),9045.310542,20132.98886,6028.427151,23214.10625 -metric_memory bandwidth total (MB/sec),25643.98869,46966.11642,19120.06995,53533.54579 -metric_ITLB (2nd level) MPI,2.75E-05,3.07E-05,2.31E-05,3.15E-05 -metric_DTLB (2nd level) load MPI,3.09E-06,3.61E-06,2.35E-06,6.11E-06 -metric_DTLB (2nd level) 2MB large page load MPI,2.49E-06,2.72E-06,2.12E-06,4.35E-06 -metric_DTLB (2nd level) store MPI,1.41E-06,1.46E-06,1.36E-06,1.47E-06 -metric_NUMA %_Reads addressed to local DRAM,99.8185312,99.8364575,99.78833974,99.84342629 -metric_NUMA %_Reads addressed to remote DRAM,0.181468803,0.206854032,0.15657371,0.21166026 -metric_uncore frequency GHz,1.222973865,1.62929913,0.79707499,1.65138266 -metric_TMA_Frontend_Bound(%),34.74955596,35.84589567,32.72219904,36.24355662 -metric_TMA_..Fetch_Latency(%),15.47372631,15.97283761,14.93029678,16.24511664 -metric_TMA_....ICache_Misses(%),0.482450248,0.555643436,0.40634481,0.56802245 -metric_TMA_....ITLB_Misses(%),0.402749702,1.345441948,0.16912789,1.81568905 -metric_TMA_....Branch_Resteers(%),9.545251611,10.11086856,8.59586407,10.18337489 -metric_TMA_......Mispredicts_Resteers(%),9.20125734,9.759604628,8.30097477,9.83468261 -metric_TMA_......Clears_Resteers(%),0.017656604,0.019805888,0.01606692,0.02037702 -metric_TMA_......Unknown_Branches(%),0.326337669,0.345813554,0.27716235,0.3569971 -metric_TMA_..Fetch_Bandwidth(%),19.27582965,20.22109013,17.14976926,20.63822685 -metric_TMA_Bad_Speculation(%),19.28407869,22.22148446,14.79514034,24.88186797 -metric_TMA_..Branch_Mispredicts(%),19.24698184,22.17606166,14.7688856,24.82694078 -metric_TMA_..Machine_Clears(%),0.037096845,0.047102298,0.02625475,0.05492718 -metric_TMA_Backend_Bound(%),6.259689102,6.695511062,6.00958893,7.07707312 -metric_TMA_..Memory_Bound(%),2.090573221,2.362368264,1.91511814,2.40909394 -metric_TMA_....L1_Bound(%),16.46835677,16.89454481,15.95972582,17.08512315 -metric_TMA_......DTLB_Load(%),2.619219689,2.83911588,2.38422941,3.15081257 -metric_TMA_......Store_Fwd_Blk(%),0.003046869,0.003495016,0.00268568,0.00355184 -metric_TMA_....L2_Bound(%),0.487683927,0.657089416,0.11870111,0.65855527 -metric_TMA_....L3_Bound(%),1.36994036,2.049051348,0.76802663,2.06385678 -metric_TMA_......Contested_Accesses(%),0.00067952,0.001019802,0.00045101,0.00108186 -metric_TMA_......Data_Sharing(%),0.000991595,0.001294612,0.0006534,0.00164725 -metric_TMA_......L3_Hit_Latency(%),0.101113219,0.117515922,0.08245768,0.12270369 -metric_TMA_......SQ_Full(%),0.040799977,0.180390682,0.01019106,0.21058348 -metric_TMA_......MEM_Bandwidth(%),0.104621263,0.399264732,0.02614526,0.58967582 -metric_TMA_......MEM_Latency(%),11.07007529,14.12880043,8.08128263,14.26036389 -metric_TMA_....Store_Bound(%),0.242391785,1.062604758,0.03371587,1.26593562 -metric_TMA_..Core_Bound(%),0,0,0,0 -metric_TMA_....Divider(%),0.467286231,0.499833534,0.40820648,0.5076326 -metric_TMA_....Ports_Utilization(%),31.6858962,32.82925136,30.44210649,32.95315178 -metric_TMA_......Ports_Utilized_0(%),0,0,0,0 -metric_TMA_......Ports_Utilized_1(%),24.06521892,24.85971663,23.20960859,24.91868299 -metric_TMA_......Ports_Utilized_2(%),19.23726601,19.78935073,18.58569962,19.99732069 -metric_TMA_......Ports_Utilized_3m(%),16.57090821,16.9282359,16.11235656,16.94057621 -metric_TMA_Retiring(%),39.70667625,40.89884978,36.31508817,41.88422992 -metric_TMA_..Light_Operations(%),39.63259781,40.82962496,36.21275428,41.81406983 -metric_TMA_..Heavy_Operations(%),0.074078442,0.096448284,0.06445111,0.10233389 -metric_TMA_Info_CoreIPC,2.260245008,2.32163854,2.18247607,2.34039858 -metric_TMA_Info_System_SMT_2T_Utilization,0.999999757,1,0.99999295,1 diff --git a/similarity-analyzer/Reference/ICX/541.csv b/similarity-analyzer/Reference/ICX/541.csv deleted file mode 100644 index 77b5f2b..0000000 --- a/similarity-analyzer/Reference/ICX/541.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.7245076425,2.7545490015,2.63380659,2.75625542 -metric_CPU utilization %,99.911650713,102.63249820000001,95.80009469,103.84920823 -metric_CPU utilization% in kernel mode,0.5229944085,0.5740762355000001,0.44720813,0.59133347 -metric_CPI,1.1646923175000004,1.1770719035,1.13075999,1.17851483 -metric_kernel_CPI,4.212781917499999,4.6410686695,3.59226414,4.64875644 -metric_IPC,0.8586817105,0.871758327,0.8485256,0.88436097 -metric_giga_instructions_per_sec,336.62225267150006,349.639488694,311.45422996,353.6308315 -metric_L1D MPI (includes data+rfo w/ prefetches),0.004550332999999999,0.0050746205,0.0039354,0.00533189 -metric_L1D demand data read hits per instr,0.28638637399999994,0.2879180215,0.28363803,0.29058812 -metric_L1-I code read misses (w/ prefetches) per instr,0.00013015600000000003,0.00015179650000000003,0.00011094,0.00016959 -metric_L2 demand data read hits per instr,0.0023291609999999997,0.0024587414999999997,0.00214871,0.00251767 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.000660077,0.0010434754999999998,0.00036745,0.00105707 -metric_L2 demand data read MPI,0.00012350250000000003,0.000175221,7.601e-05,0.00017885 -metric_L2 demand code MPI,1.4429499999999998e-05,1.80785e-05,1.07e-05,1.881e-05 -metric_Average LLC data read miss latency (in clks),230.405179366,248.77095120700002,195.94439237,250.99038865 -metric_UPI Data transmit BW (MB/sec) (only data),50.25477550200001,61.88150775300002,38.26967519,94.07950536 -metric_package power (watts),499.2886919555,502.936370266,494.06628709,513.30159675 -metric_DRAM power (watts),20.928262048,21.575200414,20.45941139,21.69981199 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),4988.3138746445,7393.345266701001,3453.14836459,7770.60437099 -metric_memory bandwidth write (MB/sec),2576.9284994104996,3813.829915752,1784.59425266,4013.33509215 -metric_memory bandwidth total (MB/sec),7565.242374055,11207.175182453,5237.74261725,11783.93946314 -metric_ITLB (2nd level) MPI,4.773649999999999e-05,5.17625e-05,4.196e-05,5.181e-05 -metric_DTLB (2nd level) load MPI,1.8055000000000003e-06,2.4145e-06,1.19e-06,2.69e-06 -metric_DTLB (2nd level) 2MB large page load MPI,3.5e-08,4e-08,3e-08,4e-08 -metric_DTLB (2nd level) store MPI,2.734e-06,3.2505e-06,2.3e-06,3.45e-06 -metric_NUMA %_Reads addressed to local DRAM,98.39306494050001,98.674939872,97.21545651,98.74657067 -metric_NUMA %_Reads addressed to remote DRAM,1.6069350595,1.8298698035000007,1.25342933,2.78454349 -metric_uncore frequency GHz,0.9416183310000001,1.24801861,0.82981232,1.35208161 -metric_TMA_Frontend_Bound(%),25.447487632499996,25.859843504,24.73010829,26.12399564 -metric_TMA_..Fetch_Latency(%),14.076222317,14.2653222495,13.21346256,14.28810571 -metric_TMA_....ICache_Misses(%),0.065940455,0.07498156,0.05603424,0.07562034 -metric_TMA_....ITLB_Misses(%),0.1846145915,0.1931487165,0.17190493,0.19335898 -metric_TMA_....Branch_Resteers(%),15.099297085999998,15.202430483,14.80746735,15.22956938 -metric_TMA_......Mispredicts_Resteers(%),14.4966307935,14.602333787,14.21562263,14.60927215 -metric_TMA_......Clears_Resteers(%),0.032479459499999995,0.0346934085,0.029263,0.03469585 -metric_TMA_......Unknown_Branches(%),0.5701868344999999,0.5804990784999999,0.55549335,0.58704075 -metric_TMA_..Fetch_Bandwidth(%),11.3712653155,12.044272785,10.62341798,12.39546489 -metric_TMA_Bad_Speculation(%),31.518382147999993,33.6821868515,28.23931132,34.3114912 -metric_TMA_..Branch_Mispredicts(%),31.447862224500007,33.602220332,28.17067911,34.23312278 -metric_TMA_..Machine_Clears(%),0.07051992399999998,0.07845253049999999,0.05940759,0.08005063 -metric_TMA_Backend_Bound(%),10.149973159500004,10.206906271000001,9.99153327,10.84634445 -metric_TMA_..Memory_Bound(%),2.9097092345,2.946285317,2.85773104,3.18585417 -metric_TMA_....L1_Bound(%),18.773379822499997,18.853301969,18.63989434,18.85717472 -metric_TMA_......DTLB_Load(%),0.645319237,0.6797550645000001,0.62096274,0.6815383 -metric_TMA_......Store_Fwd_Blk(%),0.005066696,0.006240709000000001,0.00377515,0.00785075 -metric_TMA_....L2_Bound(%),0.412652732,0.4941794760000002,0.32268181,0.73805124 -metric_TMA_....L3_Bound(%),0.570555879,0.662775334,0.41834513,0.7075947 -metric_TMA_......Contested_Accesses(%),0.0013626895000000002,0.002198228,0.00082576,0.00234183 -metric_TMA_......Data_Sharing(%),0.0024918885,0.0029596615000000003,0.00193797,0.0031286 -metric_TMA_......L3_Hit_Latency(%),0.6816311425,0.9247876685,0.2599712,1.10517383 -metric_TMA_......SQ_Full(%),0.22741931600000004,0.2829461435,0.13929831,0.37201973 -metric_TMA_......MEM_Bandwidth(%),0.6157485285000001,0.8310658635,0.33944481,0.89255221 -metric_TMA_......MEM_Latency(%),2.7987915935000003,3.2468984885000003,2.13469038,3.36966848 -metric_TMA_....Store_Bound(%),0.1899499405,0.2417374145,0.12135474,0.25696144 -metric_TMA_..Core_Bound(%),0.0163698,0.016369800000000233,0.0,0.327396 -metric_TMA_....Divider(%),1.1819574750000001,1.2611423765,1.09573466,1.33649384 -metric_TMA_....Ports_Utilization(%),26.442857395500006,26.910765086,26.01141446,27.0685317 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),20.797117436999997,20.890956427000003,20.71199614,20.91269522 -metric_TMA_......Ports_Utilized_2(%),17.130163432999996,17.323116587,16.97666084,17.44945855 -metric_TMA_......Ports_Utilized_3m(%),16.137391127500003,16.2641332995,16.01377803,16.45320514 -metric_TMA_Retiring(%),32.88415705800001,34.538214668500004,30.90053656,35.30541678 -metric_TMA_..Light_Operations(%),32.863830885000006,34.51708963400001,30.8820239,35.27711251 -metric_TMA_..Heavy_Operations(%),0.020326173000000003,0.022408085500000004,0.01851266,0.02830427 -metric_TMA_Info_CoreIPC,1.7175313970000001,1.7463743095,1.69475199,1.76276294 -metric_TMA_Info_System_SMT_2T_Utilization,0.9999969835,1.0,0.99996413,1.0 diff --git a/similarity-analyzer/Reference/ICX/548.csv b/similarity-analyzer/Reference/ICX/548.csv deleted file mode 100644 index 80d784e..0000000 --- a/similarity-analyzer/Reference/ICX/548.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.6782215341379314,2.6930284,2.65472505,2.69528535 -metric_CPU utilization %,100.71852550310345,106.30405991399999,93.70954628,123.14191149 -metric_CPU utilization% in kernel mode,0.4962913872413793,0.5307984099999999,0.45645898,0.60033232 -metric_CPI,0.8931280817241379,0.914807192,0.83465068,0.91913576 -metric_kernel_CPI,3.9421114572413796,4.060300346,2.97078067,4.11261545 -metric_IPC,1.1202022779310346,1.174858016,1.08797855,1.19810602 -metric_giga_instructions_per_sec,435.08515143413797,464.91841055199995,398.18360858,528.8684882 -metric_L1D MPI (includes data+rfo w/ prefetches),1.0176551724137927e-05,1.4237999999999995e-05,7.98e-06,2.017e-05 -metric_L1D demand data read hits per instr,0.5867548206896551,0.624113572,0.49082398,0.63224049 -metric_L1-I code read misses (w/ prefetches) per instr,0.0001613482758620689,0.00035419999999999966,6.897e-05,0.00042414 -metric_L2 demand data read hits per instr,4.2544827586206905e-06,5.7859999999999985e-06,3.32e-06,8.07e-06 -metric_L2 MPI (includes code+data+rfo w/ prefetches),1.0741034482758621e-05,1.6218e-05,5.52e-06,2.001e-05 -metric_L2 demand data read MPI,1.2403448275862068e-06,1.8699999999999995e-06,9.5e-07,2.19e-06 -metric_L2 demand code MPI,1.220689655172414e-07,2.5199999999999993e-07,3e-08,3.9e-07 -metric_Average LLC data read miss latency (in clks),918.569946858276,1021.2083033039999,471.85314644,1030.38746328 -metric_UPI Data transmit BW (MB/sec) (only data),3.42392038,5.338998631999996,2.66646712,6.37803837 -metric_package power (watts),499.50901011068964,506.32598038799995,492.02728452,514.40155952 -metric_DRAM power (watts),19.191411431724138,19.445401846,18.77559367,19.79305027 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),8.152091116206895,10.592848679999996,6.84038185,13.6991113 -metric_memory bandwidth write (MB/sec),8.169572020344829,9.564671913999998,7.11017465,12.41972433 -metric_memory bandwidth total (MB/sec),16.321663136551727,19.873480716,13.95055649,26.11883562 -metric_ITLB (2nd level) MPI,1.3596551724137933e-06,1.9259999999999994e-06,8.9e-07,2.02e-06 -metric_DTLB (2nd level) load MPI,1.0000000000000002e-07,2.4199999999999997e-07,1e-08,2.5e-07 -metric_DTLB (2nd level) 2MB large page load MPI,1.3793103448275863e-09,1e-08,0.0,1e-08 -metric_DTLB (2nd level) store MPI,1.3934482758620688e-06,1.42e-06,1.36e-06,1.43e-06 -metric_NUMA %_Reads addressed to local DRAM,13.721459612068964,27.015805387999983,5.84700716,39.86191635 -metric_NUMA %_Reads addressed to remote DRAM,86.27854038793103,92.843993958,60.13808365,94.15299284 -metric_uncore frequency GHz,0.7987808348275864,0.8101298180000001,0.78680877,0.82223505 -metric_TMA_Frontend_Bound(%),23.164537828965514,24.663870254000003,21.52902297,26.86964318 -metric_TMA_..Fetch_Latency(%),18.74772602206896,20.403885304,15.6580269,20.63768685 -metric_TMA_....ICache_Misses(%),0.015444337586206901,0.036956105999999975,0.00554869,0.04424149 -metric_TMA_....ITLB_Misses(%),0.01774375896551724,0.022242194,0.012046,0.02313009 -metric_TMA_....Branch_Resteers(%),2.3705434337931033,3.344327576,1.75742435,3.38676856 -metric_TMA_......Mispredicts_Resteers(%),2.279758450344828,3.2260053959999997,1.6771899,3.26344841 -metric_TMA_......Clears_Resteers(%),0.05093565275862067,0.055467746,0.0448417,0.05575995 -metric_TMA_......Unknown_Branches(%),0.03984933068965518,0.06614177999999998,0.0241181,0.06779634 -metric_TMA_..Fetch_Bandwidth(%),4.416811805172414,8.713605635999999,1.11516637,11.04181112 -metric_TMA_Bad_Speculation(%),7.707469480689655,11.393612013999999,2.13859063,17.13158382 -metric_TMA_..Branch_Mispredicts(%),7.537666331034482,11.196738918,2.08999251,16.8439509 -metric_TMA_..Machine_Clears(%),0.1698031510344828,0.25813142999999994,0.04859813,0.28763293 -metric_TMA_Backend_Bound(%),15.03329948862069,17.987938655999997,7.94765271,19.53991031 -metric_TMA_..Memory_Bound(%),4.284196223793104,5.210172183999999,2.18891619,5.76547585 -metric_TMA_....L1_Bound(%),13.80920167034483,13.9977029,13.40066565,14.02676366 -metric_TMA_......DTLB_Load(%),0.004089689655172414,0.006970279999999999,0.00229482,0.00777823 -metric_TMA_......Store_Fwd_Blk(%),0.001160621724137931,0.001351958,0.00105055,0.0014062 -metric_TMA_....L2_Bound(%),0.002772874827586207,0.003799398,0.00212649,0.00414025 -metric_TMA_....L3_Bound(%),0.14214915000000003,0.15407480399999998,0.1362601,0.16436256 -metric_TMA_......Contested_Accesses(%),0.0008950786206896553,0.001167596,0.00063277,0.00145653 -metric_TMA_......Data_Sharing(%),0.0013530644827586206,0.0017697239999999999,0.00101486,0.00200233 -metric_TMA_......L3_Hit_Latency(%),0.009592303793103451,0.019207871999999987,0.0077605,0.02532769 -metric_TMA_......SQ_Full(%),0.00031174103448275864,0.0006027259999999999,0.0001749,0.00066019 -metric_TMA_......MEM_Bandwidth(%),22.582366972413794,26.69991116,15.82866801,27.8154729 -metric_TMA_......MEM_Latency(%),34.41457712275862,35.646741385999995,32.75673371,37.24999391 -metric_TMA_....Store_Bound(%),0.5726192951724137,0.88938586,0.42424125,0.98933402 -metric_TMA_..Core_Bound(%),0.0,0.0,0.0,0.0 -metric_TMA_....Divider(%),0.009902247241379312,0.021105585999999992,0.00612324,0.0262728 -metric_TMA_....Ports_Utilization(%),32.3162341462069,32.787044437999995,31.61817507,33.05253423 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),20.444079632068966,20.697685056,20.07016289,20.80220116 -metric_TMA_......Ports_Utilized_2(%),21.991134746206896,22.610025344,21.4136099,22.71467648 -metric_TMA_......Ports_Utilized_3m(%),39.23433404172414,40.223030646,37.15273444,40.38221927 -metric_TMA_Retiring(%),54.09469320137932,55.326478032,51.73060976,55.62749431 -metric_TMA_..Light_Operations(%),54.07175480206896,55.30254505799999,51.70849225,55.60381125 -metric_TMA_..Heavy_Operations(%),0.022938399655172415,0.024695334,0.0219729,0.02490668 -metric_TMA_Info_CoreIPC,2.2418770282758618,2.35514003,2.17895247,2.3854082 -metric_TMA_Info_System_SMT_2T_Utilization,1.0,1.0,1.0,1.0 diff --git a/similarity-analyzer/Reference/ICX/557.csv b/similarity-analyzer/Reference/ICX/557.csv deleted file mode 100644 index 2904284..0000000 --- a/similarity-analyzer/Reference/ICX/557.csv +++ /dev/null @@ -1,70 +0,0 @@ -metrics,avg,p95,min,max -metric_CPU operating frequency (in GHz),2.83015989,2.84206681,2.81025774,2.84466007 -metric_CPU utilization %,99.89860063736842,103.84863074200001,95.61221709,104.09137327 -metric_CPU utilization% in kernel mode,0.715565595263158,0.7552060780000001,0.6788739,0.77936854 -metric_CPI,0.7840749015789474,0.799114724,0.76443562,0.8102141 -metric_kernel_CPI,6.077325448947368,6.316889491,4.96431124,6.32377513 -metric_IPC,1.2756414363157893,1.297491763,1.23424167,1.30815463 -metric_giga_instructions_per_sec,519.2775780873684,541.727667105,494.15793869,548.63074806 -metric_L1D MPI (includes data+rfo w/ prefetches),0.011195435263157895,0.013576612,0.00855348,0.01358392 -metric_L1D demand data read hits per instr,0.24636343894736842,0.25468618,0.23717675,0.25514041 -metric_L1-I code read misses (w/ prefetches) per instr,3.48778947368421e-05,5.141099999999999e-05,2.925e-05,5.205e-05 -metric_L2 demand data read hits per instr,0.007053215263157895,0.008885570999999998,0.00505615,0.00893643 -metric_L2 MPI (includes code+data+rfo w/ prefetches),0.0017825647368421053,0.0018479599999999998,0.00170822,0.0019016 -metric_L2 demand data read MPI,0.0008714826315789474,0.000976624,0.00075203,0.00098449 -metric_L2 demand code MPI,2.005947368421053e-05,2.1706e-05,1.848e-05,2.212e-05 -metric_Average LLC data read miss latency (in clks),302.3162635089473,308.238788803,293.68690137,308.82376525 -metric_UPI Data transmit BW (MB/sec) (only data),62.03296877000001,64.08668146100001,58.95356787,64.19293412 -metric_package power (watts),499.4727704557895,500.421709294,498.4380663,513.9662731 -metric_DRAM power (watts),28.273506246842107,29.375019375,27.01575032,29.64022836 -metric_core % cycles in non AVX license,100.0,100.0,100.0,100.0 -metric_core % cycles in AVX2 license,0.0,0.0,0.0,0.0 -metric_core % cycles in AVX-512 license,0.0,0.0,0.0,0.0 -metric_memory bandwidth read (MB/sec),28901.549002913685,33101.085928158,24136.00855456,33789.06459999 -metric_memory bandwidth write (MB/sec),15426.811674202632,17726.629171745997,12795.45290024,18161.12796492 -metric_memory bandwidth total (MB/sec),44328.36067711684,50827.715099903995,36931.4614548,51950.19256491 -metric_ITLB (2nd level) MPI,1.2026315789473684e-06,1.4809999999999997e-06,7.9e-07,1.58e-06 -metric_DTLB (2nd level) load MPI,1.0321052631578947e-06,1.1899999999999998e-06,8.4e-07,1.28e-06 -metric_DTLB (2nd level) 2MB large page load MPI,5.263157894736843e-10,9.999999999999788e-10,0.0,1e-08 -metric_DTLB (2nd level) store MPI,1.5747368421052635e-06,1.66e-06,1.48e-06,1.66e-06 -metric_NUMA %_Reads addressed to local DRAM,99.8417660836842,99.867167896,99.80129383,99.87214 -metric_NUMA %_Reads addressed to remote DRAM,0.15823391631578948,0.18973586099999995,0.12786,0.19870617 -metric_uncore frequency GHz,0.7985967110526316,0.8000377409999999,0.79698855,0.82178751 -metric_TMA_Frontend_Bound(%),8.22771957736842,10.149132824999995,7.12160438,12.28069755 -metric_TMA_..Fetch_Latency(%),9.998023879473687,10.616149069999999,9.26984572,10.70657198 -metric_TMA_....ICache_Misses(%),0.21890944578947366,0.22777063,0.20773508,0.22903108 -metric_TMA_....ITLB_Misses(%),0.019499750526315793,0.021932689,0.01625715,0.02235055 -metric_TMA_....Branch_Resteers(%),3.924947366315789,4.29396328,3.4966239,4.39635529 -metric_TMA_......Mispredicts_Resteers(%),3.8724989,4.241376236,3.44481023,4.34342657 -metric_TMA_......Clears_Resteers(%),0.017379657894736843,0.019779738999999998,0.01445189,0.02001103 -metric_TMA_......Unknown_Branches(%),0.03506880736842105,0.038356634,0.03162015,0.03847682 -metric_TMA_..Fetch_Bandwidth(%),0.12940900210526315,0.38697134399999544,0.0,2.28240324 -metric_TMA_Bad_Speculation(%),37.88451112578947,44.466707972,13.43619082,45.47453222 -metric_TMA_..Branch_Mispredicts(%),37.714321282105274,44.304592966,13.37186967,45.31329529 -metric_TMA_..Machine_Clears(%),0.17018984473684207,0.23399699899999998,0.06432115,0.24676241 -metric_TMA_Backend_Bound(%),19.255236006842107,26.412644579999995,16.13209133,28.15112607 -metric_TMA_..Memory_Bound(%),10.230134301578948,13.922209126999999,8.67690327,14.32341668 -metric_TMA_....L1_Bound(%),8.69247729736842,8.912659639,8.44301352,8.93020198 -metric_TMA_......DTLB_Load(%),0.5109204668421053,0.595131238,0.42814039,0.59754322 -metric_TMA_......Store_Fwd_Blk(%),0.01821221052631579,0.020060885,0.01606017,0.02009054 -metric_TMA_....L2_Bound(%),2.1603385384210525,2.93549662,1.39002922,2.93897359 -metric_TMA_....L3_Bound(%),11.595371033157896,13.369605608,9.79331691,13.38800474 -metric_TMA_......Contested_Accesses(%),0.0008932926315789472,0.0011968459999999992,0.00063377,0.00148067 -metric_TMA_......Data_Sharing(%),0.0011725957894736846,0.0016735469999999996,0.00084014,0.00181842 -metric_TMA_......L3_Hit_Latency(%),4.03971422,5.192479599,2.61800292,5.23195458 -metric_TMA_......SQ_Full(%),0.05883785473684212,0.06699107699999998,0.04567624,0.07047999 -metric_TMA_......MEM_Bandwidth(%),2.441540811052632,2.592008976,2.27400335,2.64467838 -metric_TMA_......MEM_Latency(%),35.57819142368421,36.383872172,34.86833026,36.4072874 -metric_TMA_....Store_Bound(%),0.4187662873684211,0.490900434,0.31080914,0.51047268 -metric_TMA_..Core_Bound(%),0.0,0.0,0.0,0.0 -metric_TMA_....Divider(%),0.005642168947368421,0.0056625059999999994,0.00561741,0.00567732 -metric_TMA_....Ports_Utilization(%),24.65194564736842,27.018558317,23.0756362,27.18639641 -metric_TMA_......Ports_Utilized_0(%),0.0,0.0,0.0,0.0 -metric_TMA_......Ports_Utilized_1(%),18.572209615789472,19.174991961,17.80570501,19.18140627 -metric_TMA_......Ports_Utilized_2(%),17.528205848421052,18.076631441999996,16.85280035,18.0978171 -metric_TMA_......Ports_Utilized_3m(%),19.113024303684206,19.373187928999997,18.85310119,19.38003197 -metric_TMA_Retiring(%),34.632533293157906,43.35118496399999,30.85400428,46.13198556 -metric_TMA_..Light_Operations(%),34.61680783421053,43.331758214,30.84014052,46.11050951 -metric_TMA_..Heavy_Operations(%),0.01572545684210526,0.021139531,0.01377069,0.02147605 -metric_TMA_Info_CoreIPC,2.5442796721052625,2.575700943,2.50496718,2.57663391 -metric_TMA_Info_System_SMT_2T_Utilization,1.0,1.0,1.0,1.0 diff --git a/similarity-analyzer/SimilarityAnalysis.xlsx b/similarity-analyzer/SimilarityAnalysis.xlsx deleted file mode 100644 index c6a9f35ac1c3323cdb5e9c24d4b28b4a2682d4c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100376 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 diff --git a/similarity-analyzer/_version.txt b/similarity-analyzer/_version.txt deleted file mode 100644 index 1cc5f65..0000000 --- a/similarity-analyzer/_version.txt +++ /dev/null @@ -1 +0,0 @@ -1.1.0 \ No newline at end of file diff --git a/similarity-analyzer/data_formatter/README.md b/similarity-analyzer/data_formatter/README.md deleted file mode 100644 index 93ad61e..0000000 --- a/similarity-analyzer/data_formatter/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# data_formatter - -Formats csv/xlsx data into json and helps comparing metrics from multiple csv's. - -## Example usage - -1. To compare TMA and basic metrics - -`python3 main.py -f "summary_resnet.xlsx,summary_specjbb.xlsx" -m d ` - -2. To compare all compatible metrics - -`python3 main.py -f "summary_resnet.xlsx,summary_specjbb.xlsx" -o comp1.csv` diff --git a/similarity-analyzer/data_formatter/main.py b/similarity-analyzer/data_formatter/main.py deleted file mode 100644 index a8d3b16..0000000 --- a/similarity-analyzer/data_formatter/main.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -import csv -import os -import sys -import xlrd - - -def invalid_filename(filename): - if not filename.endswith(".csv"): - raise SystemError(f"{filename} isn't a csv format!") - - -def excel2json(filename, sheet="system view"): - data = {} - try: - if os.access(filename, os.R_OK): - wb = xlrd.open_workbook(filename) - sh = wb.sheet_by_name(sheet) - for rownum in range(1, sh.nrows): - row_values = sh.row_values(rownum) - metric = row_values[0] - val = row_values[1] - data[metric] = val - else: - raise SystemExit(f"{filename} not accessible") - except xlrd.XLRDError as e: - print(e) - sys.exit(1) - return data - - -def csv2json(filename): - data = {} - try: - if os.access(filename, os.R_OK): - with open(filename, encoding="utf-8") as csvf: - csvReader = csv.DictReader(csvf) - for rows in csvReader: - key = rows["metrics"] - data[key] = float(rows["avg"]) - else: - raise SystemExit(f"{filename} not accessible") - except KeyError as invalid_csv_key: - raise SystemExit() from invalid_csv_key - return data - - -def compare_metrics(metriclist, datalist, fields, out): - if not metriclist: - metriclist = [ - m - for m in datalist[0].keys() - if m.startswith("metric_TMA") - or m - in [ - "metric_CPU operating frequency (in GHz)", - "metric_CPU utilization %", - "metric_CPU utilization% in kernel mode", - "metric_CPI", - ] - ] - print("comparing predefined metrics only") - if not invalid_filename(out): - with open(out, "w") as csvfile: - # creating a csv writer object - csvwriter = csv.writer( - csvfile, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - csvwriter.writerow(fields) - for metric in metriclist: - rowlines = [] - rowlines.append(metric) - for data in datalist: - if metric in data: - rowlines.append(data[metric]) - else: - rowlines.append("NA") - csvwriter.writerow(rowlines) - - -def compare_metrics_all(datalist, fields, out): - print("comparing all metrics in the summary sheet") - if not invalid_filename(out): - with open(out, "w") as csvfile: - # creating a csv writer object - csvwriter = csv.writer( - csvfile, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - csvwriter.writerow(fields) - allkeys = [] - for data in datalist: - for k in data: - allkeys.append(k) - uniquekeys = list(set(allkeys)) - for metric in uniquekeys: - rowlines = [] - rowlines.append(metric) - for data in datalist: - if metric in data: - rowlines.append(data[metric]) - else: - rowlines.append("NA") - csvwriter.writerow(rowlines) - - -def main(): - from argparse import ArgumentParser - - parser = ArgumentParser(description="Data Formatter") - parser.add_argument( - "-f", - "--files", - type=str, - default=None, - help='two or more excel/csv files delimited by "," and to be converted to json', - ) - parser.add_argument( - "-m", - "--metriclist", - nargs="*", - type=str, - default=[], - help='metric list to be compared (use "-m d" for default metric list)', - ) - parser.add_argument( - "-o", - "--output", - type=str, - default="compareFile.csv", - help="output file to be used", - ) - parser.add_argument( - "-p", - "--perfspect", - dest="perfspect", - default=False, - action="store_true", - help="metric.average.csv files generated by PerfSpect", - ) - - args = parser.parse_args() - if args.files: - filenames = args.files.split(",") - else: - raise SystemError("-f or --files is a required flag") - - print("comparing Files: ", filenames) - jsondata = [] - fields = ["Metric"] - for filename in filenames: - if args.perfspect: - jsondata.append(csv2json(filename)) - else: - jsondata.append(excel2json(filename)) - fields.append(filename) - - if args.metriclist: - # use "-m d" for predefined metriclist (all TMA and basic metrics) - metriclist = args.metriclist - if args.metriclist == ["d"]: - metriclist = [] - compare_metrics(metriclist, jsondata, fields, args.output) - else: - compare_metrics_all(jsondata, fields, args.output) - print("\nData compared and stored at", args.output) - - -main() diff --git a/similarity-analyzer/data_formatter/requirements.txt b/similarity-analyzer/data_formatter/requirements.txt deleted file mode 100644 index 75a0ba0..0000000 --- a/similarity-analyzer/data_formatter/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -xlrd -simplejson diff --git a/similarity-analyzer/dopca.py b/similarity-analyzer/dopca.py deleted file mode 100644 index 0b56de6..0000000 --- a/similarity-analyzer/dopca.py +++ /dev/null @@ -1,402 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2021-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### -import os -import sys -import logging -import subprocess # nosec -from argparse import ArgumentParser -import pandas as pd -import numpy as np -from pca import pca -import matplotlib.pyplot as plt -from matplotlib import cm as colmgr -from sklearn.preprocessing import StandardScaler -from sklearn.decomposition import PCA -from scipy.cluster import hierarchy - - -def verify_args(parser, args): - if not args.files: - parser.print_help() - logger.error("files is a required field") - sys.exit(1) - if args.march and args.march not in ("CLX", "ICX"): - logger.warning(f"The current released version doesn't support {args.march}") - parser.print_help() - sys.exit(1) - try: - print(args.files) - args.files = args.files.split(",") - print(args.files) - if "" in args.files: - logger.error("File name cannot be null/empty string") - sys.exit(1) - component_size = len(args.files) - if component_size in (0, 1) and not args.march: - logger.error( - f"The number of components requested is {component_size}, a minimum of 2 is required..." - ) - raise Exception - except Exception as invalid_comp_size: - raise SystemExit( - 'Minimum of 2 input files required and must contain "," delimiter between them' - ) from invalid_comp_size - if args.label: - if "" in args.label: - logger.error("label cannot be null/empty string") - parser.print_help() - sys.exit(1) - if component_size != len(args.label): - logger.warning( - f"The size of labels {args.label} don't match with input files {args.files}" - ) - parser.print_help() - sys.exit(1) - return component_size - - -def get_version(): - basepath = os.getcwd() - version_file = os.path.join(basepath, "_version.txt") - if os.access(version_file, os.R_OK): - with open(version_file) as vfile: - version = vfile.readline() - else: - raise SystemError("version file isn't accessible") - return version - - -def setup_custom_logger(name, debug): - formatter = logging.Formatter( - fmt="%(asctime)s %(levelname)-8s %(message)s", datefmt="%Y-%m-%d %H:%M:%S" - ) - handler = logging.FileHandler("log.txt", mode="w") - handler.setFormatter(formatter) - screen_handler = logging.StreamHandler(stream=sys.stdout) - screen_handler.setFormatter(formatter) - custom_logger = logging.getLogger(name) - if debug: - custom_logger.setLevel(logging.DEBUG) - else: - custom_logger.setLevel(logging.INFO) - custom_logger.addHandler(handler) - custom_logger.addHandler(screen_handler) - return custom_logger - - -def handle_nan(data, comp_size): - logger.debug("Checking for NaN in telemetry input files") - df = pd.DataFrame(data).fillna(0) - deleted_workload_profiles = [] - if not df.isnull().values.any(): - logger.debug("No NaN found in telemetry input files") - else: - logger.warning("NaN found in the input telemetry files, attempting to fix them") - df_thresh_nan = df.dropna(thresh=0.8 * len(df.columns)) - diff_df = pd.merge(df, df_thresh_nan, how="outer", indicator="Exist") - diff_df = diff_df.loc[diff_df["Exist"] != "both"] - deleted_row_indices = diff_df.index.tolist() - if deleted_row_indices: - if len(deleted_row_indices) in (comp_size, comp_size - 1): - # too many workload profiles have NaN greater than threshold, must quit similarity analysis - logger.error( - "Attempted dropping of NaNs resulted in fewer #input profiles without NaN....quitting similarity analysis" - ) - sys.exit(1) - logger.warning( - "The following input files contain NaN and will no longer be considered for similarity analysis" - ) - inp_files = args.files - for row in deleted_row_indices: - for index, filename in enumerate(inp_files): - if row == index: - comp_size = comp_size - 1 - logger.warning(f"{filename}") - if args.label: - deleted_workload_profiles.append(args.label[index]) - else: - deleted_workload_profiles.append(filename) - df = data = df_thresh_nan - if df.isnull().values.any(): - logger.debug( - f"A total of {df.isnull().sum().sum()} NaN found in your telemetry files and these will be replaced with large negative number" - ) - data = df.fillna(-99999) - return data, df.shape[0], deleted_workload_profiles - - -def add_dimension_to_data(dataset, metric_name, metric_names): - new_vec = [0] * len(metric_names) - new_vec[metric_names.index(metric_name)] = 100 - dataset.loc[len(dataset.index)] = new_vec - - -def dopca(org_dataset, metric_names, org_workload_names, dimensions): - workload_names = org_workload_names.copy() - # Make a coupy of dataset - dataset = org_dataset.copy() - dataset.columns = metric_names - print(dataset) - print(workload_names) - vec = [0] * len(metric_names) - print(vec) - print(len(vec)) - dataset.loc[len(dataset.index)] = vec - workload_names.append("Origin") - - for d in dimensions: - add_dimension_to_data(dataset, d[1], metric_names) - workload_names.append(d[0]) - dataset.index = workload_names - print("after adding dimensions") - print(dataset) - logger.info("starting PCA") - # Cleaning and separating dimensions - num_val = dataset.loc[:, metric_names].values - num_val, n_components, del_rows = handle_nan(num_val, 2) - if del_rows: - for profiles in del_rows: - try: - workload_names.remove(profiles) - except ValueError as e: - logger.error(e) - sys.exit(1) - # Normalizing the metrics - num_val = StandardScaler().fit_transform(num_val) - logger.debug(f"Post normalizing metrics, num_val: {num_val}") - # To scale to any number of workloads, generate PCAs equivalent to minimum between workloads and features - n_components = min(len(num_val), len(metric_names)) - pca = PCA(n_components=n_components) - # Transform - principal_components = pca.fit_transform(num_val) - principal_df = pd.DataFrame( - data=principal_components, - columns=["PC" + str(i) for i in range(1, n_components + 1)], - ) - logger.debug(f"explained variance ratio: {pca.explained_variance_ratio_}") - metric_df = pd.DataFrame(workload_names, columns=["Metric"]) - # concatenating the dataframe along axis = 1 - final_dataframe = pd.concat([principal_df, metric_df], axis=1) - logger.debug(f"principalDF:\n\n {principal_df}") - logger.debug(f"finalDF:\n\n {final_dataframe}") - logger.info("PCA completed") - return final_dataframe - - -def do_hierarchy(dataset, features_names, workload_names, outfile_hierarchy): - Y = hierarchy.linkage(dataset) - print("hierarchy") - print(dataset) - print(workload_names) - _ = hierarchy.dendrogram( - Y, labels=workload_names, show_leaf_counts=True, leaf_rotation=90 - ) - plt.ylabel("distance") - plt.savefig(outfile_hierarchy) - plt.clf() - logger.info(f"Hierarchy plot saved at {outfile_hierarchy}") - - -def dopca_density(dataset, features_names, workload_names, outfile_pca2): - logger.info("starting PCA-2") - - # Initialize - model = pca(normalize=True) - # Fit transform and include the column labels and row labels - dataset = dataset.reset_index(drop=True) - dataset = dataset.apply(pd.to_numeric, errors="ignore") - dataset.columns = features_names - # generate scatter plot with density and workload labels - results = model.fit_transform( - dataset, col_labels=features_names, row_labels=["0" for x in workload_names] - ) - pc_data_frame = results["PC"] - c = 0 - model.scatter(HT2=True, density=True) - for index, row in pc_data_frame.iterrows(): - plt.text(row["PC1"], row["PC2"], workload_names[c], fontsize=16) - c += 1 - plt.savefig(outfile_pca2) - plt.clf() - logger.info("PCA-2 completed") - logger.info(f"PCA_2 plot saved at {outfile_pca2}") - return results - - -# plot along PCs -def plotpca(rownames, dataframe, outfile_pca, dimensions): - logger.info("PCA plot initiated") - fig = plt.figure(figsize=(8, 8)) - plot = fig.add_subplot(1, 1, 1) - plot.set_xlabel("Principal Component 1", fontsize=15) - plot.set_ylabel("Principal Component 2", fontsize=15) - plot.set_title("Similarity Analyzer", fontsize=20) - xs = np.arange(len(rownames)) - ys = [i + xs + (i * xs) ** 2 for i in range(len(rownames))] - colors = colmgr.rainbow(np.linspace(0, 1, len(ys))) - for target, color in zip(rownames, colors): - indices_to_keep = dataframe["Metric"] == target - pc1 = dataframe.loc[indices_to_keep, "PC1"] - pc2 = dataframe.loc[indices_to_keep, "PC2"] - plot.scatter(pc1, pc2, c=color.reshape(1, -1), s=50) - plot.annotate(target, (pc1, pc2)) - plt.xlabel("PC1", fontsize=8) - plt.ylabel("PC2", fontsize=8) - plt.grid() - # add arrows - origin_vector = dataframe[dataframe["Metric"] == "Origin"] - for d in dimensions: - end_vector = dataframe[dataframe["Metric"] == d[0]] - plt.arrow( - origin_vector["PC1"].values[0], - origin_vector["PC2"].values[0], - 3 * (end_vector["PC1"].values[0] - origin_vector["PC1"].values[0]), - 3 * (end_vector["PC2"].values[0] - origin_vector["PC2"].values[0]), - length_includes_head=True, - width=0.1, - ) - - plt.savefig(outfile_pca) - plt.clf() - - logger.info(f"PCA plot saved at {outfile_pca}") - - -def get_args(): - parser = ArgumentParser(description="Similarity Analyzer") - required_arg = parser.add_argument_group("required arguments") - required_arg.add_argument( - "-f", "--files", type=str, default=None, help='excel files delimited by ","' - ) - parser.add_argument( - "-o", "--out", type=str, default="sim_workload", help="output file name" - ) - parser.add_argument( - "-d", "--debug", dest="debug", default=False, action="store_true" - ) - parser.add_argument( - "-v", "--version", help="prints the version of the tool", action="store_true" - ) - parser.add_argument( - "-m", - "--march", - help="plot pca against reference SPECcpu2017 (int_rate) components based on architecture specified. Expected values: ICX/CLX", - ) - parser.add_argument( - "-l", - "--label", - type=str, - help='label each workload profiles which will be used to plot for similarity analysis; This must map to corresponding input files delimited by ","', - ) - args = parser.parse_args() - if args.version: - print(get_version()) - sys.exit(0) - if args.label: - args.label = args.label.split(",") - print("verifying args") - verify_args(parser, args) - print("verifying args done") - - return parser.parse_args() - - -def format_data_for_PCA(args): - cmd = [] - cmd.append("python3") - cmd.append("data_formatter/main.py") - cmd.append("-f") - cmd.append(args.files) - cmd.append("-m") - cmd.append("d") - cmd.append("-o") - cmd.append(args.out + ".csv") - cmd.append("-p") - logger.debug(f"The command used by data formatter: {cmd}") - print(cmd) - with subprocess.Popen( # nosec - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) as process: - out, err = process.communicate() - if err: - logger.error(err.decode()) - sys.exit(1) - if "Data compared and stored" in str(out): - logger.info(f"data formatter collated pmu metrics at {args.out}.csv file") - else: - logger.error( - "data formatter wasn't able to collate all the pmu metrics from input files" - ) - - -def get_formatted_data_for_PCA(data_file_path): - pd_data = pd.read_csv(data_file_path) - pd_data = pd_data.rename( - columns={i: i[14:] for i in pd_data.columns if i.startswith("Reference")} - ) - if not args.label: - pd_data = pd_data.rename( - columns={i: i[:-4] for i in pd_data.columns if i != "Metric"} - ) - elif args.label: - pd_data = pd_data.rename( - columns={ - i: str(j) - for i, j in zip(pd_data.drop(columns="Metric").columns, args.label) - } - ) - return pd_data - - -if __name__ == "__main__": - args = get_args() - logger = setup_custom_logger("similarity_analyzer", args.debug) - logger.info(f"starting similarity analyzer {get_version()}") - - format_data_for_PCA(args) - - data_file_path = args.out + ".csv" - outfile = args.out + ".png" - - pd_data = get_formatted_data_for_PCA(data_file_path) - features_names = pd_data["Metric"].tolist() - - pd_data = pd_data.iloc[:, 1:] - logger.debug(f"dataset before transpose:\n {pd_data}") - - workload_names = list(pd_data.columns) - - pd_data = pd_data.T - pd_data = pd_data.reset_index(drop=True) - - pd_data.insert(loc=0, column="metric", value=workload_names) - pd_data.set_index("metric", inplace=True) - - logger.debug(f"dataset post transpose:\n {pd_data}") - features_index = pd_data.columns.tolist() - - pd_data = pd_data.fillna(0) - - # PCA - dimensions = [ - ("Front-end", "metric_TMA_Frontend_Bound(%)"), - ("Back-end", "metric_TMA_Backend_Bound(%)"), - ] - final_df = dopca(pd_data, features_names, workload_names, dimensions) - row_names = final_df["Metric"].values - outfile_pca = args.out + "_pca.png" - plotpca(row_names, final_df, outfile_pca, dimensions) - - # PCA with density - outfile_pca_density = args.out + "_pca_density.png" - final_df = dopca_density( - pd_data, features_names, workload_names, outfile_pca_density - ) - - # Hierarchy - outfile_hierarchy = args.out + "_hierarchy.png" - do_hierarchy(pd_data, features_index, workload_names, outfile_hierarchy) diff --git a/similarity-analyzer/requirements.txt b/similarity-analyzer/requirements.txt deleted file mode 100644 index 6675382..0000000 --- a/similarity-analyzer/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -matplotlib -pandas -scikit_learn -simplejson -xlrd -pca -scipy \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 38bbf18..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2020-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -__all__ = ["perf_helpers", "prepare_perf_events.py"] diff --git a/src/base.html b/src/base.html deleted file mode 100644 index 49e0b1a..0000000 --- a/src/base.html +++ /dev/null @@ -1,830 +0,0 @@ - - - - - - PerfSpect - - - - - - - - - - - - - - - -
- - - - \ No newline at end of file diff --git a/src/calibrate.c b/src/calibrate.c deleted file mode 100644 index b6b97cc..0000000 --- a/src/calibrate.c +++ /dev/null @@ -1,38 +0,0 @@ -//########################################################################################################## -// Copyright (C) 2020-2023 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -//########################################################################################################### - -#include -#include - -typedef unsigned long long uint64_t; - -static __inline__ uint64_t rdtsc_s(void) -{ - unsigned a=0; - unsigned d=0; - asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); - asm volatile("rdtsc" : "=a" (a), "=d" (d)); - return ((unsigned long)a) | (((unsigned long)d) << 32); -} - -static __inline__ uint64_t rdtsc_e(void) -{ - unsigned a=0; - unsigned d=0; - asm volatile("rdtscp" : "=a" (a), "=d" (d)); - asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); - return ((unsigned long)a) | (((unsigned long)d) << 32); -} - -unsigned Calibrate(void){ - uint64_t start=rdtsc_s(); - sleep(1); - uint64_t end=rdtsc_e(); - - uint64_t clocks_mhz= (end-start)/1000000; - unsigned tsc_mhz = clocks_mhz; - return tsc_mhz; -} - diff --git a/src/common.py b/src/common.py deleted file mode 100644 index 1eae35d..0000000 --- a/src/common.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging -import sys -import os - - -def crash(msg): - logging.error(msg) - sys.exit(1) - - -def configure_logging(output_dir): - run_file = os.path.split(sys.argv[0])[1] - program_name = os.path.splitext(run_file)[0] - logfile = f"{os.path.join(output_dir, program_name)}.log" - # create the log file if it doesn't already exist so that we can allow - # writes from any user in case program is run under sudo and then later - # without sudo - if not os.path.exists(logfile): - with open(logfile, "w+"): - pass - os.chmod(logfile, 0o666) # nosec - logging.basicConfig( - level=logging.NOTSET, - format="%(asctime)s %(levelname)s: %(message)s", - handlers=[logging.FileHandler(logfile), logging.StreamHandler(sys.stdout)], - ) - return logfile diff --git a/src/perf_helpers.py b/src/perf_helpers.py deleted file mode 100644 index ae2ab48..0000000 --- a/src/perf_helpers.py +++ /dev/null @@ -1,438 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2020-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -import collections -import fnmatch -import logging -import os -import re -import subprocess # nosec -import time -from ctypes import cdll, CDLL -from datetime import datetime -from dateutil import tz -from src.common import crash -from time import strptime - - -version = "PerfSpect_DEV_VERSION" - - -# get tool version info -def get_tool_version(): - return str(version) - - -# extract number of sockets -def get_socket_count(): - cpuinfo = get_cpuinfo() - return int(cpuinfo[-1]["physical id"]) + 1 - - -# extract number of hyperthreads -def get_ht_count(): - cpuinfo = get_cpuinfo() - return int(any([core["siblings"] != core["cpu cores"] for core in cpuinfo])) + 1 - - -# get hyperthreading status -def get_ht_status(): - cpuinfo = get_cpuinfo() - return any([core["siblings"] != core["cpu cores"] for core in cpuinfo]) - - -# get cpu count -def get_cpu_count(): - cpu_count = 0 - if not os.path.isfile("/sys/devices/system/cpu/online"): - crash("/sys/devices/system/cpu/online not found to get core count") - with open("/sys/devices/system/cpu/online", "r") as f_online_cpu: - content = f_online_cpu.read() - cpu_list = content.split(",") - for c in cpu_list: - limit = c.split("-") - cpu_count += int(limit[1]) - int(limit[0]) + 1 - return int(cpu_count / (get_socket_count() * get_ht_count())) - - -# compute tsc frequency -def get_tsc_freq(): - script_path = os.path.dirname(os.path.realpath(__file__)) - tsclib = script_path + "/libtsc.so" - cdll.LoadLibrary(tsclib) - tsc = CDLL(tsclib) - tsc_freq = str(tsc.Calibrate()) - if tsc_freq == 0: - crash("can't calculate TSC frequency") - return tsc_freq - - -def get_dev_name(name): - name = name.strip() - parts = name.split("_") - if parts[-1].isdigit(): - newlen = len(name) - len(parts[-1]) - 1 - name = name[:newlen] - return name - - -# get sys/devices mapped files -def get_sys_devices(): - devs = {} - for f in os.listdir("/sys/devices"): - name = get_dev_name(f.strip()) - if name not in devs: - devs[name] = 1 - else: - devs[name] = devs[name] + 1 - return devs - - -# get relevant uncore device counts -# TODO:fix for memory config with some channels populated -def get_unc_device_counts(): - sys_devs = get_sys_devices() - counts = {} - if "uncore_cha" in sys_devs: - counts["cha"] = int(sys_devs["uncore_cha"]) - elif "uncore_cbox" in sys_devs: # alternate name for cha - counts["cha"] = int(sys_devs["uncore_cbox"]) - else: - counts["cha"] = 0 - - if "uncore_upi" in sys_devs: - counts["upi"] = int(sys_devs["uncore_upi"]) - elif "uncore_qpi" in sys_devs: # alternate name for upi - counts["upi"] = int(sys_devs["uncore_qpi"]) - else: - counts["upi"] = 0 - - if "uncore_imc" in sys_devs: - counts["imc"] = int(sys_devs["uncore_imc"]) - else: - counts["imc"] = 0 - - if "uncore_b2cmi" in sys_devs: - counts["b2cmi"] = int(sys_devs["uncore_b2cmi"]) - else: - counts["b2cmi"] = 0 - return counts - - -# return a sorted list of device ids for a given device type pattern, e.g., uncore_cha_, uncore_imc_, etc. -# note: this is necessary because device ids are not always consecutive -def get_device_ids(pattern): - sysdevices = os.listdir("/sys/bus/event_source/devices") - devices = pattern + "[0-9]*" - ids = [] - for entry in sysdevices: - if fnmatch.fnmatch(entry, devices): - words = entry.split("_") - ids.append(int(words[-1])) - ids = sorted(ids) - return ids - - -# get perf event mux interval for pmu events -def get_perf_event_mux_interval(): - mux_interval = {} - for f in os.listdir("/sys/devices"): - dirpath = os.path.join("/sys/devices/", f) - if os.path.isdir(dirpath): - muxfile = os.path.join(dirpath, "perf_event_mux_interval_ms") - if os.path.isfile(muxfile): - with open(muxfile, "r") as f_mux: - mux_interval[f] = f_mux.read() - return mux_interval - - -# Returns true/false depending on state of the NMI watchdog timer, or None on error. -def nmi_watchdog_enabled(): - try: - proc_output = subprocess.check_output(["cat", "/proc/sys/kernel/nmi_watchdog"]) - except (subprocess.CalledProcessError, FileNotFoundError) as e: - logging.warning(f"Failed to get nmi_watchdog status: {e}") - return None - try: - nmi_watchdog_status = int(proc_output.decode().strip()) - except ValueError as e: - logging.warning(f"Failed to interpret nmi_watchdog status: {e}") - return None - return nmi_watchdog_status == 1 - - -# disable nmi watchdog and return its initial status -# to restore it after collection -def disable_nmi_watchdog(): - nmi_watchdog_status = nmi_watchdog_enabled() - if nmi_watchdog_status is None: - logging.error("Failed to get nmi_watchdog status.") - return None - try: - if nmi_watchdog_status: - proc_output = subprocess.check_output( - ["sysctl", "kernel.nmi_watchdog=0"], stderr=subprocess.STDOUT - ) - new_watchdog_status = int( - proc_output.decode().strip().replace("kernel.nmi_watchdog = ", "") - ) - if new_watchdog_status != 0: - crash("Failed to disable nmi watchdog.") - logging.info( - "nmi_watchdog temporarily disabled. Will re-enable after collection." - ) - else: - logging.info("nmi_watchdog already disabled. No change needed.") - return nmi_watchdog_status - except (ValueError, FileNotFoundError, subprocess.CalledProcessError) as e: - logging.warning(f"Failed to disable nmi_watchdog: {e}") - - -def check_perf_event_paranoid(): - try: - return int( - subprocess.check_output(["cat", "/proc/sys/kernel/perf_event_paranoid"]) - ) - except (ValueError, FileNotFoundError, subprocess.CalledProcessError) as e: - logging.warning(f"Failed to check perf_event_paranoid: {e}") - - -# enable nmi watchdog -def enable_nmi_watchdog(): - try: - proc_output = subprocess.check_output(["sysctl", "kernel.nmi_watchdog=1"]) - new_watchdog_status = int( - proc_output.decode().strip().replace("kernel.nmi_watchdog = ", "") - ) - if new_watchdog_status != 1: - logging.warning("Failed to re-enable nmi_watchdog.") - else: - logging.info("nmi_watchdog re-enabled.") - except (ValueError, FileNotFoundError, subprocess.CalledProcessError) as e: - logging.warning(f"Failed to re-enable nmi_watchdog: {e}") - - -# set/reset perf event mux interval for pmu events -def set_perf_event_mux_interval(reset, interval_ms, mux_interval): - for f in os.listdir("/sys/devices"): - dirpath = os.path.join("/sys/devices/", f) - if os.path.isdir(dirpath): - muxfile = os.path.join(dirpath, "perf_event_mux_interval_ms") - if os.path.isfile(muxfile): - try: - with open(muxfile, "w") as f_mux: - val = 0 - if reset: - val = int(mux_interval[f]) - else: - if int(mux_interval[f]): - val = int(interval_ms) - if val: - f_mux.write(str(val)) - except OSError as e: - logging.warning(f"Failed to write mux interval: {e}") - break - - -# get linux kernel version -def get_version(): - version = "" - try: - with open("/proc/version", "r") as f: - version = f.read() - version = version.split("#")[0] - except EnvironmentError as e: - logging.warning(str(e), UserWarning) - return version - - -# populate the CPU info list after reading /proc/cpuinfo in list of dictionaries -def get_cpuinfo(): - cpuinfo = [] - temp_dict = {} - try: - with open("/proc/cpuinfo", "r") as fo: - for line in fo: - try: - key, value = list(map(str.strip, line.split(":", 1))) - except ValueError: - cpuinfo.append(temp_dict) - temp_dict = {} - else: - temp_dict[key] = value - except EnvironmentError as e: - logging.warning(str(e), UserWarning) - return cpuinfo - - -def get_lscpu(): - cpuinfo = {} - try: - lscpu = subprocess.check_output(["lscpu"]).decode() # nosec - lscpu = [i for i in lscpu.split("\n") if i] - for prop in lscpu: - key, value = prop.split(":") - value = value.lstrip() - cpuinfo[key] = value - except subprocess.CalledProcessError as e: - crash(e.output + "\nFailed to get CPUInfo") - return cpuinfo - - -# get supported perf events -def get_perf_list(): - try: - perf_list = subprocess.check_output(["perf", "list"]).decode() # nosec - except FileNotFoundError: - crash("Please install Linux perf and re-run") - except subprocess.CalledProcessError as e: - crash(f"Error calling Linux perf, error code: {e.returncode}") - return perf_list - - -def get_arch_and_name(procinfo): - arch = modelname = "" - try: - model = int(procinfo[0]["model"].strip()) - cpufamily = int(procinfo[0]["cpu family"].strip()) - stepping = int(procinfo[0]["stepping"].strip()) - vendor = str(procinfo[0]["vendor_id"].strip()) - modelname = procinfo[0]["model name"].strip() - except KeyError: - # for non-Intel architectures - cpuinfo = get_lscpu() - modelname = str(cpuinfo["Model name"]) - stepping = str(cpuinfo["Stepping"]) - vendor = str(cpuinfo["Vendor ID"]) - if vendor == "GenuineIntel": - if model == 85 and cpufamily == 6 and stepping == 4: - arch = "skylake" - elif model == 85 and cpufamily == 6 and stepping >= 5: - arch = "cascadelake" - elif model == 79 and cpufamily == 6 and stepping == 1: - arch = "broadwell" - elif model == 106 and cpufamily == 6 and stepping >= 4: - arch = "icelake" - elif model == 143 and cpufamily == 6 and stepping >= 3: - arch = "sapphirerapids" - elif model == 207 and cpufamily == 6: - arch = "emeraldrapids" - elif model == 175 and cpufamily == 6: - arch = "sierraforest" - elif (model == 173 or model == 174) and cpufamily == 6: # GNR and GNR-D - arch = "graniterapids" - return arch, modelname - - -# Get CPUs(as seen by OS) associated with each socket -def get_cpuid_info(procinfo): - if "vendor_id" in procinfo[0].keys(): - vendor = str(procinfo[0]["vendor_id"].strip()) - else: - vendor = "Non-Intel" - socketinfo = collections.OrderedDict() - for proc in procinfo: - if vendor == "GenuineIntel": - key = proc["physical id"] - else: - key = 0 - val = proc["processor"] - if socketinfo.get(key) is None: - socketinfo.setdefault(key, [val]) - else: - socketinfo[key].append(val) - return socketinfo - - -# check write permissions on file, or directory if file doesn't exist -def check_file_writeable(outfile): - if os.path.exists(outfile): - if os.path.isfile(outfile): - return os.access(outfile, os.W_OK) - else: - return False - dirname = os.path.dirname(outfile) - if not dirname: - dirname = "." - return os.access(dirname, os.W_OK) - - -# convert time to epoch -def get_epoch(start_time): - words = "".join(start_time).split() - month = words[4] - date = words[5] - year = words[7] - month = str(strptime(month, "%b").tm_mon) - # os.environ['TZ']='UTC' - utc = tz.tzutc() - utc_info = str(datetime.utcnow().replace(tzinfo=utc).astimezone(tz.tzlocal())) - timestamp = ( - year + "-" + str(month) + "-" + date + " " + words[6] + " " + utc_info[-6:] - ) - timestamp_utc_format = re.sub(r"([+-]\d+):(\d+)$", r"\1\2", timestamp) - epoch = int( - time.mktime(time.strptime(timestamp_utc_format, "%Y-%m-%d %H:%M:%S %z")) - ) - return epoch - - -# get_cgroups -# cid: a comma-separated list of container ids -# note: works only for cgroup v2 -def get_cgroups(cid): - # check cgroup version - if not os.path.exists("/sys/fs/cgroup/cgroup.controllers"): - crash("cgroup v1 detected, cgroup v2 required") - # get cgroups from /sys/fs/cgroup directory recursively. They must start with 'docker' or 'containerd' and end with '.scope'. - # if cid is provided, only return cgroups that match the provided container ids - cids = cid.split(",") - cgroups = [] - # get all cgroups - for dirpath, dirnames, filenames in os.walk("/sys/fs/cgroup"): - for dirname in dirnames: - if ( - ("docker" in dirname or "containerd" in dirname) - and dirname.endswith(".scope") - and (len(cids) == 0 or any(map(lambda y: y in dirname, cids))) - ): - cgroups.append( - os.path.relpath(os.path.join(dirpath, dirname), "/sys/fs/cgroup") - ) - # associate cgroups with their cpu utilization found in the usage_usec field of the cgroup's cpu.stat file - cgroup_cpu_usage = {} - for cgroup in cgroups: - try: - with open(f"/sys/fs/cgroup/{cgroup}/cpu.stat", "r") as f: - for line in f: - if "usage_usec" in line: - cgroup_cpu_usage[cgroup] = int(line.split()[1]) - except EnvironmentError as e: - logging.warning(str(e), UserWarning) - # sort cgroups by cpu usage, highest usage first - cgroups = sorted(cgroup_cpu_usage, key=cgroup_cpu_usage.get, reverse=True) - - if len(cgroups) == 0: - crash("no matching cgroups found") - elif len(cgroups) > 5: - logging.warning( - "more than 5 matching cgroups found removing: " + str(cgroups[5:]) - ) - cgroups = cgroups[:5] - for c in cgroups: - logging.info("attaching to cgroup: " + c) - return cgroups - - -def get_pmu_driver_version(): - command = "dmesg | grep -A 1 'Intel PMU driver' | tail -1 | awk '{print $NF}'" - try: - version_number = subprocess.check_output(command, shell=True).decode().strip() - return version_number - except subprocess.CalledProcessError as e: - print(f"Error executing command: {e}") - return None diff --git a/src/prepare_perf_events.py b/src/prepare_perf_events.py deleted file mode 100644 index f839186..0000000 --- a/src/prepare_perf_events.py +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env python3 - -########################################################################################################### -# Copyright (C) 2020-2023 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -########################################################################################################### - -import logging -import os -import re -import src.perf_helpers as helper -from src.common import crash - - -# test if the event can be collected, check supported events in perf list -def is_collectable_event(event, perf_list): - tmp_list = event.split("/") - name = helper.get_dev_name(tmp_list[0]) - unc_name = "uncore_" + name - sys_devs = helper.get_sys_devices() - dev_support = False - if name in sys_devs or unc_name in sys_devs: - dev_support = True - if ( - dev_support - and len(tmp_list) > 1 - and (tmp_list[1].startswith("umask") or tmp_list[1].startswith("event")) - ): - return True - - test_event = event.split(";")[0] - test_event = test_event.split(",")[0] - test_event = test_event.split(":")[0] - pattern = r"s*" + test_event - if re.search(pattern, perf_list, re.MULTILINE): - return True - else: - return False - - -# expand uncore event names -def expand_unc(line): - line = line.strip() - name = line.split("/")[0] - unc_name = "uncore_" + name - ids = helper.get_device_ids(unc_name + "_") - unc_count = len(ids) - if unc_count > 1: - line = line.replace(name, unc_name + "_" + str(ids[0])) - if "name=" in line: - prettyname = (line.split("'"))[1].strip() - line = line.replace(prettyname, prettyname + "." + str(ids[0])) - return line, unc_count - - -# check if CPU/core event -def is_cpu_event(line): - line = line.strip() - tmp_list = line.split("/") - # assumes event name without a PMU qualifier is a core event - if ( - (len(tmp_list) == 1 or tmp_list[0] == "cpu" or tmp_list[0].startswith("cstate")) - and "OCR." not in line - and "uops_retired.ms" not in line.lower() - and "int_misc.unknown_branch_cycles" not in line.lower() - and "power/" not in line - ): - return True - return False - - -# enumerate uncore events across all devices -def enumerate_uncore(group, pattern, count): - uncore_group = "" - ids = helper.get_device_ids(pattern) - for i in range(count - 1): - old = pattern + str(ids[i]) - new = pattern + str(ids[i + 1]) - group = group.replace(old, new) - oldname = group.split("'") - for j, n in enumerate(oldname): - if "name=" in n: - tmp = oldname[j + 1] - idx = tmp.rfind(".") - oldname[j + 1] = tmp[: idx + 1] + str(ids[i + 1]) - - group = "'".join(oldname) - uncore_group += group - return uncore_group - - -# For multiple cgroup collection we need this format : ā€œ-e e1 -e e2 -G foo,foo -G bar,bar" -def get_cgroup_events_format(cgroups, events, num_events): - eventlist = "" - grouplist = "" - # "-e" flags: Create event groups as many number of cgroups - for _ in range(len(cgroups)): - eventlist += " -e " + events - - # "-G" flags: Repeat cgroup name for as many events in each event group - for cgroup in cgroups: - grouplist = grouplist.rstrip(",") + " -G " - for _ in range(num_events): - grouplist += cgroup + "," - - perf_format = eventlist + grouplist.rstrip(",") - return perf_format - - -def filter_events( - event_file, - cpu_only, - supports_tma_fixed_events, - supports_uncore_events, - supports_ref_cycles, -): - if not os.path.isfile(event_file): - crash("event file not found") - collection_events = [] - unsupported_events = [] - perf_list = helper.get_perf_list() - # seperate_cycles = [] - # if not supports_uncore_events: - # # since most CSP's hide cycles fixed PMU inside their VM's we put it in its own group - # if supports_ref_cycles: - # seperate_cycles = [ - # "cpu-cycles,", - # "cpu-cycles:k,", - # "ref-cycles,", - # "instructions;", - # ] - # else: - # seperate_cycles = [ - # "cpu-cycles,", - # "cpu-cycles:k,", - # "instructions;", - # ] - - def process(line): - line = line.strip() - if line == "" or line.startswith("#") or (cpu_only and not is_cpu_event(line)): - return - if not supports_tma_fixed_events and ( - "name='TOPDOWN.SLOTS'" in line or "name='PERF_METRICS." in line - ): - return - if not is_collectable_event(line, perf_list): - # not a collectable event - unsupported_events.append(line) - # if this is the last event in the group, mark the previous event as the last (with a ';') - if line.endswith(";") and len(collection_events) > 1: - end_event = collection_events[-1] - collection_events[-1] = end_event[:-1] + ";" - else: - collection_events.append(line) - - with open(event_file, "r") as fin: - for line in fin: - # if in_vm and "cpu-cycles" in line: - # continue - if not supports_ref_cycles and "ref-cycles" in line: - continue - process(line) - # for line in seperate_cycles: - # process(line) - if len(unsupported_events) > 0: - logging.warning( - f"Perf unsupported events not counted: {unsupported_events}" - ) - return collection_events, unsupported_events - - -def prepare_perf_events( - event_file, - cpu_only, - supports_tma_fixed_events, - supports_uncore_events, - supports_ref_cycles, -): - start_group = "'{" - end_group = "}'" - group = "" - prev_group = "" - new_group = True - - collection_events, unsupported_events = filter_events( - event_file, - cpu_only, - supports_tma_fixed_events, - supports_uncore_events, - supports_ref_cycles, - ) - core_event = [] - uncore_event = [] - event_names = [] - for line in collection_events: - if cpu_only: - if is_cpu_event(line): - event = line + ":c" - core_event.append(event) - else: - if is_cpu_event(line): - event = line + ":c" - core_event.append(event) - else: - event = line + ":u" - uncore_event.append(event) - event_names.append(event) - line, unc_count = expand_unc(line) - if new_group: - group += start_group - prev_group = start_group - new_group = False - if line.endswith(";"): - group += line[:-1] + end_group + "," - prev_group += line[:-1] + end_group + "," - new_group = True - else: - group += line - prev_group += line - - # enumerate all uncore units - if new_group and (unc_count > 1): - name = helper.get_dev_name(line.split("/")[0].strip()) - group += enumerate_uncore(prev_group, name + "_", unc_count) - - group = group[:-1] - if len(event_names) == 0: - crash("No supported events found on this platform.") - # being conservative not letting the collection to proceed if fixed counters aren't supported on the platform - if len(unsupported_events) >= len(core_event): - crash( - "Most core counters aren't supported on this platform, unable to collect PMUs" - ) - return group, event_names diff --git a/staticcheck.conf b/staticcheck.conf new file mode 100644 index 0000000..2e19f27 --- /dev/null +++ b/staticcheck.conf @@ -0,0 +1,4 @@ +checks = ["all"] +initialisms = [] # ["ACL", "API", "ASCII", "CPU", "CSS", "DIMM", "DNS", "EOF", "GPU", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "IRQ", "ISA", "JSON", "NIC", "PMU", "OS", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"] +dot_import_whitelist = [] # ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"] +http_status_code_whitelist = [] # ["200", "400", "404", "500"] \ No newline at end of file diff --git a/targets.yaml b/targets.yaml new file mode 100644 index 0000000..4cd77b5 --- /dev/null +++ b/targets.yaml @@ -0,0 +1,29 @@ +# This YAML file contains a list of remote targets with their corresponding properties. +# Each target has the following properties: +# name: The name of the target (optional) +# host: The IP address or host name of the target (required) +# port: The port number used to connect to the target via SSH (optional) +# user: The user name used to connect to the target via SSH (optional) +# key: The path to the private key file used to connect to the target via SSH (optional) +# pwd: The password used to connect to the target via SSH (optional) +# +# Note: If key and pwd are both provided, the key will be used for authentication. +# +# Security Notes: +# It is recommended to use a private key for authentication instead of a password. +# Keep this file in a secure location and do not expose it to unauthorized users. +# +# Below are examples. Modify them to match your environment. +targets: + - name: ELAINES_TARGET + host: 192.168.1.1 + port: + user: elaine + key: /home/elaine/.ssh/id_rsa + pwd: + - name: JERRYS_TARGET + host: 192.168.1.2 + port: 2222 + user: jerry + key: + pwd: george diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..1c41a85 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,229 @@ +#!make +# +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# + +default: tools +.PHONY: default tools async-profiler avx-turbo cpuid dmidecode ethtool flamegraph ipmitool lshw lspci msr-tools pcm perf spectre-meltdown-checker sshpass stress-ng sysstat tsc turbostat + +tools: async-profiler avx-turbo cpuid dmidecode ethtool flamegraph ipmitool lshw lspci msr-tools pcm perf spectre-meltdown-checker sshpass stress-ng sysstat tsc turbostat + mkdir -p bin + cp -R async-profiler bin/ + cp avx-turbo/avx-turbo bin/ + cp cpuid/cpuid bin/ + cp dmidecode/dmidecode bin/ + cp ethtool/ethtool bin/ + cp flamegraph/stackcollapse-perf.pl bin/ + cp ipmitool/src/ipmitool.static bin/ipmitool + cp lshw/src/lshw-static bin/lshw + cp lspci/lspci bin/ + cp lspci/pci.ids.gz bin/ + cp msr-tools/rdmsr bin/ + cp msr-tools/wrmsr bin/ + cp pcm/build/bin/pcm-tpmi bin/ + cp pcm/scripts/bhs-power-mode.sh bin/ + cp linux_perf/tools/perf/perf bin/ + cp spectre-meltdown-checker/spectre-meltdown-checker.sh bin/ + cp sshpass/sshpass bin/ + cp stress-ng/stress-ng bin/ + cp sysstat/mpstat bin/ + cp sysstat/iostat bin/ + cp sysstat/sar bin/ + cp sysstat/sadc bin/ + cp tsc/tsc bin/ + cp linux_turbostat/tools/power/x86/turbostat/turbostat bin/ + -cd bin && strip --strip-unneeded * + +ASYNCPROFILER_VERSION := 2.9 +async-profiler: +ifeq ("$(wildcard async-profiler)","") +ifeq ("$(wildcard async-profiler-$(ASYNCPROFILER_VERSION)-linux-x64.tar.gz)","") + wget https://github.com/jvm-profiling-tools/async-profiler/releases/download/v$(ASYNCPROFILER_VERSION)/async-profiler-$(ASYNCPROFILER_VERSION)-linux-x64.tar.gz +endif + tar -xf async-profiler-$(ASYNCPROFILER_VERSION)-linux-x64.tar.gz && mv async-profiler-$(ASYNCPROFILER_VERSION)-linux-x64 async-profiler +endif + +avx-turbo: +ifeq ("$(wildcard avx-turbo)","") + git clone https://github.com/harp-intel/avx-turbo.git +endif + cd avx-turbo && git checkout threadcpuid && git pull + cd avx-turbo && make + +# if you change the version, check the sed hacks below +CPUID_VERSION := 20240409 +cpuid: +ifeq ("$(wildcard cpuid)","") +ifeq ("$(wildcard cpuid-$(CPUID_VERSION).src.tar.gz)","") + wget http://www.etallen.com/cpuid/cpuid-$(CPUID_VERSION).src.tar.gz +endif + tar -xf cpuid-$(CPUID_VERSION).src.tar.gz && mv cpuid-$(CPUID_VERSION)/ cpuid/ +endif + # gcc 4.8 doesn't support -Wimplicit-fallthrough option + cd cpuid && sed -i s/"-Wimplicit-fallthrough"/""/ Makefile + cd cpuid && make + +dmidecode: +ifeq ("$(wildcard dmidecode)","") + git clone https://github.com/mirror/dmidecode.git +else + cd dmidecode && git checkout master && git pull +endif + cd dmidecode && git checkout dmidecode-3-5 + cd dmidecode && make + +ethtool: +ifeq ("$(wildcard ethtool)","") + git clone https://git.kernel.org/pub/scm/network/ethtool/ethtool.git +else + cd ethtool && git checkout master && git pull +endif + cd ethtool && git checkout v6.5 +ifeq ("$(wildcard ethtool/Makefile)","") + cd ethtool && ./autogen.sh && ./configure enable_netlink=no +endif + cd ethtool && make + +flamegraph: +ifeq ("$(wildcard flamegraph)","") + git clone https://github.com/brendangregg/FlameGraph.git flamegraph + # small modification to script to include module name in output + cd flamegraph && sed -i '382 a \\t\t\t\t$$func = \$$func."'" "'".\$$mod;\t# add module name' stackcollapse-perf.pl +endif + +ipmitool: +ifeq ("$(wildcard ipmitool)","") + git clone https://github.com/ipmitool/ipmitool.git +endif + cd ipmitool && git checkout IPMITOOL_1_8_19 +ifeq ("$(wildcard ipmitool/Makefile)","") + # hack to get around static build problem - don't check for libreadline + sed -i "s#x\$$xenable_ipmishell#xno#" ipmitool/configure.ac + cd ipmitool && ./bootstrap && LDFLAGS=-static ./configure +endif + cd ipmitool && make + cd ipmitool/src && ../libtool --silent --tag=CC --mode=link gcc -fno-strict-aliasing -Wreturn-type -all-static -o ipmitool.static ipmitool.o ipmishell.o ../lib/libipmitool.la plugins/libintf.la + +lshw: +ifeq ("$(wildcard lshw)","") + git clone https://github.com/lyonel/lshw.git +else + cd lshw && git checkout master && git pull +endif + cd lshw && git checkout B.02.19 + cd lshw/src && make static + +lspci: +ifeq ("$(wildcard lspci)","") + git clone https://github.com/pciutils/pciutils.git lspci +else + cd lspci && git checkout master && git pull +endif + cd lspci && make + cd lspci && ./update-pciids.sh + cd lspci && gzip -c pci.ids > pci.ids.gz + +msr-tools: +ifeq ("$(wildcard msr-tools)","") + git clone https://github.com/intel/msr-tools.git +else + cd msr-tools && git checkout master && git pull +endif + cd msr-tools && ./autogen.sh && make + +pcm: +ifeq ("$(wildcard pcm)","") + git clone --recursive https://github.com/intel/pcm.git +else + cd pcm && git checkout master && git pull +endif + mkdir -p pcm/build + cd pcm/build && cmake -DNO_ASAN=1 .. + cd pcm/build && cmake --build . + +PERF_LINUX_VERSION := 6.1.52 +perf: + wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$(PERF_LINUX_VERSION).tar.xz + tar -xf linux-$(PERF_LINUX_VERSION).tar.xz && mv linux-$(PERF_LINUX_VERSION)/ linux_perf/ + cd linux_perf/tools/perf && make LDFLAGS="-static --static" + +spectre-meltdown-checker: +ifeq ("$(wildcard spectre-meltdown-checker)","") + git clone https://github.com/speed47/spectre-meltdown-checker.git +else + cd spectre-meltdown-checker && git checkout master && git pull +endif + +SSHPASS_VERSION := 1.10 +sshpass: +ifeq ("$(wildcard sshpass)","") + wget https://sourceforge.net/projects/sshpass/files/sshpass/$(SSHPASS_VERSION)/sshpass-$(SSHPASS_VERSION).tar.gz + tar -xf sshpass-$(SSHPASS_VERSION).tar.gz + mv sshpass-$(SSHPASS_VERSION) sshpass + rm sshpass-$(SSHPASS_VERSION).tar.gz + cd sshpass && ./configure +endif + cd sshpass && make + +stress-ng: +ifeq ("$(wildcard stress-ng)","") + git clone https://github.com/ColinIanKing/stress-ng.git +else + cd stress-ng && git checkout master && git pull +endif + cd stress-ng && git checkout V0.13.08 + cd stress-ng && STATIC=1 make + +sysstat: +ifeq ("$(wildcard sysstat)","") + git clone https://github.com/sysstat/sysstat.git +else + cd sysstat && git checkout master && git pull +endif +ifeq ("$(wildcard sysstat/Makefile)","") + cd sysstat && ./configure +endif + cd sysstat && make + +tsc: + cd tsc && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build + +TURBOSTAT_LINUX_VERSION := 6.9.12 +turbostat: + wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$(TURBOSTAT_LINUX_VERSION).tar.xz + tar -xf linux-$(TURBOSTAT_LINUX_VERSION).tar.xz && mv linux-$(TURBOSTAT_LINUX_VERSION)/ linux_turbostat/ + sed -i '/_Static_assert/d' linux_turbostat/tools/power/x86/turbostat/turbostat.c + cd linux_turbostat/tools/power/x86/turbostat && make + +reset: + cd async-profiler + cd cpuid && make clean + cd dmidecode && git clean -fdx && git reset --hard + cd ethtool && git clean -fdx && git reset --hard + cd flamegraph && git clean -fdx && git reset --hard + cd ipmitool && git clean -fdx && git reset --hard + cd lshw && git clean -fdx && git reset --hard + cd lspci && git clean -fdx && git reset --hard + cd pcm && git clean -fdx && git reset --hard + cd msr-tools && git clean -fdx && git reset --hard + cd linux_perf/tools/perf && make clean + cd spectre-meltdown-checker + cd sshpass && make clean + cd stress-ng && git clean -fdx && git reset --hard + cd sysstat && git clean -fdx && git reset --hard + cd tsc && rm -f tsc + cd linux_turbostat/tools/power/x86/turbostat && make clean + +# not used in build but required in oss archive file because some of the tools are statically linked +glibc-2.19.tar.bz2: + wget http://ftp.gnu.org/gnu/glibc/glibc-2.19.tar.bz2 +zlib.tar.gz: + wget https://github.com/madler/zlib/archive/refs/heads/master.tar.gz -O zlib.tar.gz +libcrypt.tar.gz: + wget https://github.com/gpg/libgcrypt/archive/refs/heads/master.tar.gz -O libcrypt.tar.gz +libs: glibc-2.19.tar.bz2 zlib.tar.gz libcrypt.tar.gz + +oss-source: reset libs + tar --exclude-vcs -czf oss_source.tgz async-profiler/ cpuid/ dmidecode/ ethtool/ flamegraph/ ipmitool/ lshw/ lspci/ msr-tools/ pcm/ linux_perf/tools/perf spectre-meltdown-checker/ sshpass/ stress-ng/ sysstat/ linux_turbostat/tools/power/x86/turbostat glibc-2.19.tar.bz2 zlib.tar.gz libcrypt.tar.gz + md5sum oss_source.tgz > oss_source.tgz.md5 diff --git a/tools/build.Dockerfile b/tools/build.Dockerfile new file mode 100644 index 0000000..8bb928e --- /dev/null +++ b/tools/build.Dockerfile @@ -0,0 +1,39 @@ +# Copyright (C) 2021-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# builds tools used by the project +# build output binaries will be in workdir/bin +# build output oss_source* will be in workdir +# build image (from project root directory): +# $ docker build -f tools/build.Dockerfile --tag perfspect-tools:$TAG ./tools +FROM ubuntu:18.04 AS builder +ENV http_proxy=${http_proxy} +ENV https_proxy=${https_proxy} +ENV LANG=en_US.UTF-8 +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y apt-utils locales wget curl git netcat-openbsd software-properties-common jq zip unzip +RUN locale-gen en_US.UTF-8 && echo "LANG=en_US.UTF-8" > /etc/default/locale +RUN add-apt-repository ppa:git-core/ppa -y +RUN apt-get update && apt-get install -y git build-essential autotools-dev automake \ + gawk zlib1g-dev libtool libaio-dev libaio1 pandoc pkgconf libcap-dev docbook-utils \ + libreadline-dev default-jre default-jdk cmake flex bison libssl-dev + +ENV JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64 + +# need golang to build go tools +RUN rm -rf /usr/local/go && wget -qO- https://go.dev/dl/go1.23.0.linux-amd64.tar.gz | tar -C /usr/local -xz +ENV PATH="${PATH}:/usr/local/go/bin" + +# need up-to-date zlib (used by stress-ng static build) to fix security vulnerabilities +RUN git clone https://github.com/madler/zlib.git && cd zlib && ./configure && make install +RUN cp /usr/local/lib/libz.a /usr/lib/x86_64-linux-gnu/libz.a + +# Build third-party components +RUN mkdir workdir +ADD . /workdir +WORKDIR /workdir +RUN make tools && make oss-source + +FROM scratch AS output +COPY --from=builder workdir/bin /bin +COPY --from=builder workdir/oss_source* / diff --git a/tools/tsc/go.mod b/tools/tsc/go.mod new file mode 100644 index 0000000..9c9efcc --- /dev/null +++ b/tools/tsc/go.mod @@ -0,0 +1,3 @@ +module intel.com/tsc + +go 1.23.0 diff --git a/tools/tsc/tsc.go b/tools/tsc/tsc.go new file mode 100644 index 0000000..35cafd3 --- /dev/null +++ b/tools/tsc/tsc.go @@ -0,0 +1,11 @@ +package main + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import "fmt" + +func main() { + freq := GetTSCFreqMHz() + fmt.Printf("%d", freq) +} diff --git a/tools/tsc/tsc_amd64.go b/tools/tsc/tsc_amd64.go new file mode 100644 index 0000000..aae33d7 --- /dev/null +++ b/tools/tsc/tsc_amd64.go @@ -0,0 +1,22 @@ +// Time Stamp Counter helper functions. +package main + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +import ( + "time" +) + +// GetTSCFreqMHz - gets the TSC frequency +func GetTSCFreqMHz() (freqMHz int) { + start := GetTSCStart() + time.Sleep(time.Millisecond * 1000) + end := GetTSCEnd() + freqMHz = int(end-start) / 1000000 + return +} + +func GetTSCStart() uint64 + +func GetTSCEnd() uint64 diff --git a/tools/tsc/tsc_amd64.s b/tools/tsc/tsc_amd64.s new file mode 100644 index 0000000..cb8ca79 --- /dev/null +++ b/tools/tsc/tsc_amd64.s @@ -0,0 +1,27 @@ + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// Adapted from https://github.com/dterei/gotsc/blob/master/tsc_amd64.s +// Copyright 2016 David Terei. All rights reserved. + +#include "textflag.h" + +// func GetTSCStart() uint64 +TEXT Ā·GetTSCStart(SB),NOSPLIT,$0-8 + CPUID + RDTSC + SHLQ $32, DX + ADDQ DX, AX + MOVQ AX, ret+0(FP) + RET + +// func GetTSCEnd() uint64 +TEXT Ā·GetTSCEnd(SB),NOSPLIT,$0-8 + BYTE $0x0F // RDTSCP + BYTE $0x01 + BYTE $0xF9 + SHLQ $32, DX + ADDQ DX, AX + MOVQ AX, ret+0(FP) + CPUID + RET diff --git a/tools/tsc/tsc_arm64.go b/tools/tsc/tsc_arm64.go new file mode 100644 index 0000000..8737694 --- /dev/null +++ b/tools/tsc/tsc_arm64.go @@ -0,0 +1,16 @@ +package main + +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +func GetTSCStart() uint64 { + return 0 +} + +func GetTSCEnd() uint64 { + return 0 +} + +func GetTSCFreqMHz() (freqMHz int) { + return 0 +} diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..4a36342 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +3.0.0