From 7fec1b7dc4273e7799a43c21e31da1fb4e379ddc Mon Sep 17 00:00:00 2001 From: Petre Tudor Date: Fri, 18 Jul 2025 13:12:22 +0100 Subject: [PATCH 1/2] Rust rt-app proof of concept Implements the current rt-app 'mem' workload to show how an rt-app event would be added to the new (v2) Rust version. There are two frontends: "v2" and "compat". The purpose of a frontend is to take a configuration (CLI + JSON) and convert it to an internal representation of the execution plan that rt-app will follow when running the events. The execution plan is further converted to an [almost identical] program which consists of executable "instructions". The two frontends are completely separated, such that each has their own: - high level (JSON/YAML) configuration style - converting pass from high level configuration to internal execution plan (low level config) - CLI (and logic for parsing, managing default values etc.) This will allow the "v2" frontend to be developed freely, while still offering backwards compatibility with current rt-app configs and scripts via the "compat" code path. Users can select between the "v2" and "compat" frontends via the subcommands 'run' and 'run-compat', respectively. If none is supplied, the subcommand defaults to: - 'run-compat' if the binary is executed under the 'rt-app' name (e.g. by using a symlink) - 'run' otherwise. The README.md file contains instructions for building and running the project. Signed-off-by: Petre Tudor --- tools/rt-app-v2/.gitignore | 1 + tools/rt-app-v2/.rustfmt.toml | 2 + tools/rt-app-v2/Cargo.lock | 303 ++++++++++++++++++ tools/rt-app-v2/Cargo.toml | 14 + tools/rt-app-v2/README.md | 143 +++++++++ tools/rt-app-v2/examples/config.json | 13 + tools/rt-app-v2/examples/config_compat.json | 13 + tools/rt-app-v2/src/lib/cli.rs | 145 +++++++++ .../rt-app-v2/src/lib/high_level/frontend.rs | 128 ++++++++ .../src/lib/high_level/frontend_compat.rs | 76 +++++ .../src/lib/high_level/frontend_v2.rs | 72 +++++ tools/rt-app-v2/src/lib/high_level/mod.rs | 19 ++ tools/rt-app-v2/src/lib/instr.rs | 217 +++++++++++++ tools/rt-app-v2/src/lib/lib.rs | 20 ++ tools/rt-app-v2/src/lib/low_level.rs | 67 ++++ tools/rt-app-v2/src/main.rs | 41 +++ 16 files changed, 1274 insertions(+) create mode 100644 tools/rt-app-v2/.gitignore create mode 100644 tools/rt-app-v2/.rustfmt.toml create mode 100644 tools/rt-app-v2/Cargo.lock create mode 100644 tools/rt-app-v2/Cargo.toml create mode 100644 tools/rt-app-v2/README.md create mode 100644 tools/rt-app-v2/examples/config.json create mode 100644 tools/rt-app-v2/examples/config_compat.json create mode 100644 tools/rt-app-v2/src/lib/cli.rs create mode 100644 tools/rt-app-v2/src/lib/high_level/frontend.rs create mode 100644 tools/rt-app-v2/src/lib/high_level/frontend_compat.rs create mode 100644 tools/rt-app-v2/src/lib/high_level/frontend_v2.rs create mode 100644 tools/rt-app-v2/src/lib/high_level/mod.rs create mode 100644 tools/rt-app-v2/src/lib/instr.rs create mode 100644 tools/rt-app-v2/src/lib/lib.rs create mode 100644 tools/rt-app-v2/src/lib/low_level.rs create mode 100644 tools/rt-app-v2/src/main.rs diff --git a/tools/rt-app-v2/.gitignore b/tools/rt-app-v2/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/tools/rt-app-v2/.gitignore @@ -0,0 +1 @@ +/target diff --git a/tools/rt-app-v2/.rustfmt.toml b/tools/rt-app-v2/.rustfmt.toml new file mode 100644 index 000000000..69a377c96 --- /dev/null +++ b/tools/rt-app-v2/.rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity="Crate" +group_imports="StdExternalCrate" diff --git a/tools/rt-app-v2/Cargo.lock b/tools/rt-app-v2/Cargo.lock new file mode 100644 index 000000000..f047236d5 --- /dev/null +++ b/tools/rt-app-v2/Cargo.lock @@ -0,0 +1,303 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "clap" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rt-app-v2" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "serde", + "serde_json", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/tools/rt-app-v2/Cargo.toml b/tools/rt-app-v2/Cargo.toml new file mode 100644 index 000000000..bf5d79efe --- /dev/null +++ b/tools/rt-app-v2/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rt-app-v2" +version = "0.1.0" +edition = "2024" + +[lib] +name = "lib" +path = "src/lib/lib.rs" + +[dependencies] +anyhow = "~1.0" +clap = { version = "~4.5", features = ["derive"] } +serde = { version = "~1.0", features = ["derive"] } +serde_json = "~1.0" diff --git a/tools/rt-app-v2/README.md b/tools/rt-app-v2/README.md new file mode 100644 index 000000000..ba0847e22 --- /dev/null +++ b/tools/rt-app-v2/README.md @@ -0,0 +1,143 @@ +# rt-app-v2 : A Rustified rt-app + +## Description + +This is a very minimal prototype for a Rust version of rt-app, meant to serve as proof of concept for how the full version will work. + +It implements the current rt-app memory workload ('mem' event) as an example of how events can be added in this Rust version. + +There is also a compatibility high level configuration, meant to illustrate how multiple frontends can be used. The current rt-app configuration features will be ported here for backwards compatibility. + +Both the v2 and compatibility frontend have their own CLI, such that no changes to config or command line are required when running rt-app-v2 in compatibility mode. + +## Motivation + +Certain aspects of rt-app could benefit from a refresh, e.g.: + +- configuration quirks: cpus not being handled like any other key, coupling between priority and policy etc. +- bugs linked to propagating the current state to the next phase +- boilerplate resulting from dealing with JSON in C + +Combined, such aspects complicate the addition new events and features to rt-app. + +In addition, an internal representation of the low level execution plan can be used as "compilation" target not just by rt-app frontends, but by other external tools. + +It would also be nice to have errors reported will full context when the input is ill-formed (e.g. versus being told simply that `sched_setattr()` returned `-1` when the user sets a priority value for a **SCHED_DEADLINE** task). + +## Approach + +The approach used here is the compiler pattern: + +- a frontend takes a (CLI, high level JSON/YAML config) pair and generates the low level internal representation +- the low level configuration is converted to a runnable "instruction" format + +The main advantage of this approach is that the low level representation can be a target for multiple frontends. Backwards compatibility with current rt-app configurations can be implemented as a frontend. + +This also means full freedom to develop the v2 frontend according to current needs, although we don't foresee major changes to the CLI and high level config. + +Selecting between the two frontends (v2 and compatibility) is done via an optional subcommand (e.g. `exec rt-app-v2 run-compat "$@"` to run in compatibility mode). The subcommand will default to selecting the v2 frontend, so it need not be supplied when running in default mode. Additionally, to preserve compatibility with old scripts, when invoked as "rt-app" (e.g. via symlink), compatibility mode is selected regardless of the subcommand. + +### High level configuration + +The high-level configuration will be similar to current rt-app, but with each phase fully independent from the others. + +Logic for managing default values will be simplified. + +Each high level configuration node is converted to one or more low level statements. + +### Low level configuration + +Structured as a simple imperative language - list of statements where each syscall has an associated action. Can be printed out as a low level execution plan. + +Examples: + +- Current rt-app events are also actions +- A sub-program is a list of actions +- A loop is an event with a loop counter and an associated sub-program +- A fork is an event with a task name and an associated sub-program +- An action to emit a line of log in a ftrace marker or on stderr +- An action to increment a counter, with support in the log action to dump its value + +This internal representation is lowered to a list of instructions. This representation is nearly identical to the low level configuration, the subtle difference being that it holds only the resources needed to execute these statements. + +The Memset event provides an example of such a difference: the low level Memset node holds metadata about the event (the buffer size), but the Memset instruction requires a pointer to a memory region of that size and consequently does not need to track the buffer size separately. + +## Getting started + +To build and run this project, you will need an up to date Rust environment: + +If you already have Rust installed, consider updating it by running `rustup update`. So far, this project was developed using rustc >= 1.85. + +## Usage + +``` +Usage: rt-app-v2 + +Commands: + run Selects the v2 frontend + run-compat Selects the compatibility frontend + help Print this message or the help of the given subcommand(s) +``` + +### Running the v2 example + +Run `cargo build` and then execute the binary: + +```bash +$ cargo build +$ ./target/debug/rt-app-v2 examples/config.json +Running Memset { count: 10, buf_size: 4194304 } +Running Memset { count: 10, buf_size: 2097152 } +``` + +or, alternatively: + +```bash +$ cargo run -- examples/config.json + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s + Running `target/debug/rt-app-v2 examples/config.json` +Running Memset { count: 10, buf_size: 4194304 } +Running Memset { count: 10, buf_size: 2097152 } +``` + +### Running the compatibility example + +Run `cargo build` and then execute the binary: + +```bash +$ cargo build +$ ./target/debug/rt-app-v2 run-compat examples/config_compat.json +Running Memset { count: 10, buf_size: 4194304 } +Running Memset { count: 10, buf_size: 2097152 } +``` + +or, alternatively: + +```bash +$ cargo run -- run-compat examples/config_compat.json + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s + Running `target/debug/rt-app-v2 examples/config.json` +Running Memset { count: 10, buf_size: 4194304 } +Running Memset { count: 10, buf_size: 2097152 } +``` + +For full compatibility with the rt-app command line, the program can be called under the 'rt-app' name and it will automatically select the compatibility frontend: + +```bash +$ cargo build +$ ln -s target/debug/rt-app-v2 rt-app +$ ./rt-app examples/config_compat.json +Running Memset { count: 10, buf_size: 4194304 } +Running Memset { count: 10, buf_size: 2097152 } +``` + +## Roadmap + +- Implement the existing rt-app workloads. +- Port the current rt-app configuration to the "legacy" frontend. +- Try to implement Rust [FFI](https://doc.rust-lang.org/nomicon/ffi.html) bindings to basic musl libc functions. +- Try to implement an event that executes an assembly snippet. + +## License + +This project is licensed under Apache-2.0 (). diff --git a/tools/rt-app-v2/examples/config.json b/tools/rt-app-v2/examples/config.json new file mode 100644 index 000000000..a824abe36 --- /dev/null +++ b/tools/rt-app-v2/examples/config.json @@ -0,0 +1,13 @@ +{ +"tasks": [ + { + "type": "Memset", + "count": 10 + }, + { + "type" : "Memset", + "count": 10, + "buf_size": 2097152 + } +] +} diff --git a/tools/rt-app-v2/examples/config_compat.json b/tools/rt-app-v2/examples/config_compat.json new file mode 100644 index 000000000..c45f4e19f --- /dev/null +++ b/tools/rt-app-v2/examples/config_compat.json @@ -0,0 +1,13 @@ +{ +"tasks": [ + { + "type": "Mem", + "count": 10 + }, + { + "type" : "Mem", + "count": 10, + "buf_size": 2097152 + } +] +} diff --git a/tools/rt-app-v2/src/lib/cli.rs b/tools/rt-app-v2/src/lib/cli.rs new file mode 100644 index 000000000..6dfe290c9 --- /dev/null +++ b/tools/rt-app-v2/src/lib/cli.rs @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! CLI implementation for rt-app-v2. +//! +//! Implements subcommands which allow the user to select between available frontends: +//! - v2, which implements the new functionality. +//! - compatibility mode, in which rt-app-v2 behaves like the current C Version +//! (from command line arguments down to parsing the JSON config). + +use std::env; + +use anyhow::{Ok, Result, anyhow}; +use clap::{Parser, Subcommand}; + +use crate::{ + high_level::{ + frontend::Frontend, + frontend_compat::{FrontendCompat, RunCompatArgs}, + frontend_v2::{FrontendV2, RunArgs}, + }, + low_level::LowLevelConfig, +}; + +const COMPAT_EXE_NAME: &str = "rt-app"; + +#[derive(Debug, Subcommand)] +enum CliSub { + /// Selects the v2 frontend. + /// + /// This is the default if no subcommand is present, unless the program is called under + /// the 'rt-app' name, which selects the compatibility frontend. + Run(RunArgs), + /// Selects the compatibility frontend. + /// + /// This frontend implements the old rt-app behaviours relative to configuration + /// (including command line arguments). + /// + /// If the binary is called with the 'rt-app' name (e.g. via symlink), this frontend is + /// automatically selected. + RunCompat(RunCompatArgs), +} + +#[derive(Parser, Debug)] +pub struct Cli { + #[command(subcommand)] + subcommand: CliSub, +} + +impl Cli { + pub fn build_cli_args() -> Result { + let mut cli_args = env::args().collect::>(); + + /* + * std::env::current_exe() treats symlinks in a platform-specific way. + * + * On Linux, it follows the symlink and returns the path to the target executable. + * To work around this, try to use the first argv and fall back to std::env::current_exe(). + * + * See https://github.com/rust-lang/rust/issues/43617 + */ + let exe = match std::env::args().next() { + Some(ref s) if !s.is_empty() => { + let path = std::path::PathBuf::from(s); + if !path.is_file() { + std::env::current_exe().unwrap() + } else { + path + } + } + _ => { + let e = std::env::current_exe().unwrap(); + cli_args.push(e.to_string_lossy().to_string()); + + e + } + }; + let exe_name = exe.file_name().unwrap(); + + let subcommand_present = ["run", "run-compat", "help"] + .iter() + .any(|&x| cli_args.contains(&x.to_string())); + + assert!(!cli_args.is_empty()); + + if exe_name == COMPAT_EXE_NAME { + // Prepend the 'run-compat' subcommand to the command line. This mode is meant to provide + // full compatibility with older scripts and, as such, it does not expect subcommands. + if subcommand_present { + return Err(anyhow!( + "Executable name is '{}'. This mode is meant to provide full compatibility \ + with older scripts and, as such, it does not expect a subcommand to be present.", + COMPAT_EXE_NAME + )); + } + + cli_args.insert(1, "run-compat".to_string()); + } else if !subcommand_present { + // If no subcommand is present and the executable name is not 'rt-app', select the v2 + // frontend. + cli_args.insert(1, "run".to_string()); + } + + Ok(Cli::parse_from(cli_args)) + } +} + +/// Frontend dispatcher - selects which frontend to use based on the subcommand present in the CLI. +pub enum FrontendKind { + V2(FrontendV2), + Compat(FrontendCompat), +} + +impl Frontend for FrontendKind { + fn build(args: Cli) -> Result { + let c = match args.subcommand { + CliSub::Run(a) => FrontendKind::V2(FrontendV2::build(a)?), + CliSub::RunCompat(a) => FrontendKind::Compat(FrontendCompat::build(a)?), + }; + + Ok(c) + } + + fn lower(self) -> Result { + let l = match self { + FrontendKind::V2(c) => c.lower()?, + FrontendKind::Compat(c) => c.lower()?, + }; + + Ok(l) + } +} diff --git a/tools/rt-app-v2/src/lib/high_level/frontend.rs b/tools/rt-app-v2/src/lib/high_level/frontend.rs new file mode 100644 index 000000000..d977626de --- /dev/null +++ b/tools/rt-app-v2/src/lib/high_level/frontend.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! High level configuration which allows for the existence of multiple frontends, all internally +//! converting to the same low level config representation. + +use anyhow::Result; + +pub trait Frontend +where + Self: Sized, +{ + fn build(args: T) -> Result; + fn lower(self) -> Result; +} + +/// Generates the definition and trait implementations for an Enum where each variant is a +/// different kind of high level config node. +macro_rules! define_config { + ($name:ident, {$($variant:ident),* $(,)?}) => { + #[derive(Debug, Serialize, Deserialize)] + #[serde(tag = "type")] + pub enum $name { + $( + $variant($variant) + ),* + } + + impl TryFrom<$name> for LowLevelNode { + type Error = anyhow::Error; + + fn try_from(high_level: $name) -> core::result::Result { + match high_level { + $( + $name::$variant(x) => { + x.try_into() + } + ),* + } + } + } + + #[derive(Debug, Serialize, Deserialize)] + #[serde(tag = "type")] + pub struct Config { + pub tasks: Vec<$name>, + } + + impl Config { + pub fn parse_json( + file: &std::path::PathBuf, + ) -> core::result::Result { + let data = std::fs::read_to_string(file) + .with_context(|| format!("Could not read from `{file:?}`"))?; + + let c: Config = + serde_json::from_str(&data).with_context(|| "Failed to parse JSON".to_string())?; + + Ok(c) + } + } + + impl TryFrom for LowLevelConfig { + type Error = anyhow::Error; + + fn try_from(high_level: Config) -> core::result::Result { + let mut c = LowLevelConfig::new(); + + for node in high_level.tasks { + c.nodes.push(node.try_into()?); + } + + Ok(c) + } + } + } +} +pub(crate) use define_config; + +macro_rules! define_frontend { + ($name:ident, $cli:ident) => { + pub struct $name { + config: Config, + cli_args: $cli, + } + + impl Frontend<$cli> for $name { + fn build(args: $cli) -> Result { + let c = Config::parse_json(&args.file) + .with_context(|| "Could not build config from JSON".to_string())?; + + Ok($name { + config: c, + cli_args: args, + }) + } + + fn lower(self) -> Result { + let l = LowLevelConfig::try_from(self.config) + .with_context(|| "Could not lower to low level representation".to_string())?; + + if let Some(json_output) = &self.cli_args.dump_json { + l.write_to_file(json_output).with_context(|| { + "Could not write low level representation to JSON".to_string() + })?; + } + + Ok(l) + } + } + }; +} +pub(crate) use define_frontend; + +use crate::low_level::LowLevelConfig; diff --git a/tools/rt-app-v2/src/lib/high_level/frontend_compat.rs b/tools/rt-app-v2/src/lib/high_level/frontend_compat.rs new file mode 100644 index 000000000..529c534e8 --- /dev/null +++ b/tools/rt-app-v2/src/lib/high_level/frontend_compat.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! Second frontend, implementing the old rt-app configuration style for backwards compatibility. + +use anyhow::{Context, Result}; +use clap::Parser; +use serde::{Deserialize, Serialize}; + +use super::frontend::{Frontend, define_frontend}; +use crate::{ + high_level::frontend::define_config, + low_level::{self, LowLevelConfig, LowLevelNode}, +}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct RunCompatArgs { + /// Relative path to JSON config file + #[arg(index = 1)] + file: std::path::PathBuf, + + /// Relative path to destination file for dumping the resulting low-level config in JSON format + #[arg(short, long)] + dump_json: Option, +} + +/// rt-app 'mem' event. Called 'Mem' (vs. 'mem') for now so we can play nicely with serde and get +/// the parsing for free. This is just to show how multiple frontends can exist. +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub struct Mem { + pub count: usize, + + #[serde(default = "Mem::default_buf_size")] + pub buf_size: usize, +} + +impl TryFrom for LowLevelNode { + type Error = anyhow::Error; + + fn try_from(high_level: Mem) -> core::result::Result { + Ok(LowLevelNode::Memset(low_level::Memset { + count: high_level.count, + buf_size: high_level.buf_size, + })) + } +} + +impl Mem { + pub fn default_buf_size() -> usize { + 4 * 1024 * 1024 + } +} + +define_config! { + ConfigCompatNode, + { + Mem, + } +} + +define_frontend! {FrontendCompat, RunCompatArgs} diff --git a/tools/rt-app-v2/src/lib/high_level/frontend_v2.rs b/tools/rt-app-v2/src/lib/high_level/frontend_v2.rs new file mode 100644 index 000000000..2aafefb2a --- /dev/null +++ b/tools/rt-app-v2/src/lib/high_level/frontend_v2.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! Frontend implementation for the new high level configuration style. + +use anyhow::{Context, Result}; +use clap::Parser; +use serde::{Deserialize, Serialize}; + +use super::frontend::{Frontend, define_frontend}; +use crate::{ + high_level::frontend::define_config, + low_level::{self, LowLevelConfig, LowLevelNode}, +}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct RunArgs { + /// Relative path to JSON config file + #[arg(index = 1)] + file: std::path::PathBuf, + + /// Relative path to destination file for dumping the resulting low-level config in JSON format + #[arg(short, long)] + dump_json: Option, +} + +const MEMSET_DEFAULT_BUFFER_SIZE: usize = 4 * 1024 * 1024; + +/// Equivalent to the current rt-app 'mem' event. +/// +/// If left unspecified in the JSON config, the buffer size will be set to +/// MEMSET_DEFAULT_BUFFER_SIZE during conversion to the corresponding low level config node. +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub struct Memset { + pub count: usize, + pub buf_size: Option, +} + +impl TryFrom for LowLevelNode { + type Error = anyhow::Error; + + fn try_from(high_level: Memset) -> core::result::Result { + Ok(LowLevelNode::Memset(low_level::Memset { + count: high_level.count, + buf_size: high_level.buf_size.unwrap_or(MEMSET_DEFAULT_BUFFER_SIZE), + })) + } +} + +define_config! { + ConfigV2Node, + { + Memset, + } +} + +define_frontend! {FrontendV2, RunArgs} diff --git a/tools/rt-app-v2/src/lib/high_level/mod.rs b/tools/rt-app-v2/src/lib/high_level/mod.rs new file mode 100644 index 000000000..05c5484c1 --- /dev/null +++ b/tools/rt-app-v2/src/lib/high_level/mod.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +pub mod frontend; +pub mod frontend_compat; +pub mod frontend_v2; diff --git a/tools/rt-app-v2/src/lib/instr.rs b/tools/rt-app-v2/src/lib/instr.rs new file mode 100644 index 000000000..2605ffaa2 --- /dev/null +++ b/tools/rt-app-v2/src/lib/instr.rs @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! Backend implementation. +//! +//! A program is a list of executable instructions, lowered from the low level config +//! representation. + +use std::{cell::RefCell, cmp, fmt, ptr}; + +use anyhow::{Ok, Result}; + +use crate::low_level::{self, LowLevelConfig, LowLevelNode}; + +pub trait Run { + fn run(&self) -> Result<()>; +} + +#[derive(Debug)] +pub struct Memset { + count: usize, + buf: RefCell>, +} + +impl Run for Memset { + fn run(&self) -> Result<()> { + unsafe { + let vec_ptr = self.buf.borrow_mut().as_mut_ptr(); + let len = self.buf.borrow().len(); + let mut remaining_count = self.count; + + while remaining_count > 0 { + let count = cmp::min(remaining_count, len); + ptr::write_bytes(vec_ptr, 0xfe, count); + + remaining_count -= count; + } + } + + Ok(()) + } +} + +impl TryFrom for Memset { + type Error = anyhow::Error; + + fn try_from(node: low_level::Memset) -> core::result::Result { + let m = Memset { + count: node.count, + buf: RefCell::new(vec![0u8; node.buf_size]), + }; + + Ok(m) + } +} + +impl fmt::Display for Memset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Memset") + .field("count", &self.count) + .field("buf_size", &self.buf.borrow().len()) + .finish() + } +} + +#[derive(Debug)] +pub struct SubProgram { + instructions: Vec, +} + +impl Run for SubProgram { + fn run(&self) -> Result<()> { + for instr in &self.instructions { + instr.run()?; + } + + Ok(()) + } +} + +impl TryFrom for SubProgram { + type Error = anyhow::Error; + + fn try_from(group: low_level::SubProgram) -> core::result::Result { + let mut g = SubProgram { + instructions: vec![], + }; + + for n in group.nodes { + g.instructions.push(n.try_into()?); + } + + Ok(g) + } +} + +impl fmt::Display for SubProgram { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.instructions.iter()).finish() + } +} + +/// Generates the definition and trait implementations for an Enum where each variant is a +/// particular kind of instruction. +macro_rules! define_instr { + ($name:ident, {$($variant:ident),* $(,)?}) => { + #[derive(Debug)] + pub enum $name { + $( + $variant($variant) + ),* + } + + impl Run for $name { + fn run(&self) -> Result<()> { + println!("Running {self}"); + match self { + $( + $name::$variant(instr) => instr.run()? + ),* + } + + Ok(()) + } + } + + impl TryFrom for $name { + type Error = anyhow::Error; + + fn try_from(node: LowLevelNode) -> core::result::Result { + let i = match node { + $( + LowLevelNode::$variant(instr) => $name::$variant(instr.try_into()?) + ),* + }; + + Ok(i) + } + } + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $( + $name::$variant(t) => write!(f, "{}", t) + ),* + } + } + } + + } +} + +define_instr! { + Instruction, + { + Memset, + SubProgram, + } +} + +/// Executable program, lowered from a LowLevelConfig. +#[derive(Debug)] +pub struct Program { + pub instructions: Vec, +} + +impl Program { + pub fn new() -> Program { + Program { + instructions: vec![], + } + } +} + +impl Default for Program { + fn default() -> Self { + Self::new() + } +} + +impl Run for Program { + fn run(&self) -> Result<()> { + for instr in &self.instructions { + instr.run()? + } + + Ok(()) + } +} + +impl TryFrom for Program { + type Error = anyhow::Error; + + fn try_from(config: LowLevelConfig) -> core::result::Result { + let mut p = Program::new(); + + for node in config.nodes { + p.instructions.push(node.try_into()?); + } + + Ok(p) + } +} diff --git a/tools/rt-app-v2/src/lib/lib.rs b/tools/rt-app-v2/src/lib/lib.rs new file mode 100644 index 000000000..7a740a323 --- /dev/null +++ b/tools/rt-app-v2/src/lib/lib.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +pub mod cli; +pub mod high_level; +pub mod instr; +pub mod low_level; diff --git a/tools/rt-app-v2/src/lib/low_level.rs b/tools/rt-app-v2/src/lib/low_level.rs new file mode 100644 index 000000000..44bb64543 --- /dev/null +++ b/tools/rt-app-v2/src/lib/low_level.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! Low level internal representation of an rt-app configuration. +//! +//! This is intended as a simple imperative language to be used as target for a frontend which +//! takes a high level configuration as input. +//! +//! This representation is lowered to a program composed of nearly identical instructions which +//! can be executed. + +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Memset { + pub count: usize, + pub buf_size: usize, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SubProgram { + pub nodes: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum LowLevelNode { + Memset(Memset), + SubProgram(SubProgram), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LowLevelConfig { + pub nodes: Vec, +} + +impl LowLevelConfig { + pub fn new() -> LowLevelConfig { + LowLevelConfig { nodes: vec![] } + } + + pub fn write_to_file(&self, file: &std::path::PathBuf) -> Result<()> { + let data = serde_json::to_string(&self) + .with_context(|| "Failed to serialize to JSON string".to_string())?; + std::fs::write(file, data).with_context(|| format!("Failed to write to `{file:?}`")) + } +} + +impl Default for LowLevelConfig { + fn default() -> Self { + Self::new() + } +} diff --git a/tools/rt-app-v2/src/main.rs b/tools/rt-app-v2/src/main.rs new file mode 100644 index 000000000..a082c352e --- /dev/null +++ b/tools/rt-app-v2/src/main.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +use anyhow::{Context, Ok, Result}; +use lib::{ + cli::{Cli, FrontendKind}, + high_level::frontend::Frontend, + instr::{Program, Run}, +}; + +fn main() -> Result<()> { + let cli = Cli::build_cli_args().with_context(|| "Could not build command line".to_string())?; + + let f = FrontendKind::build(cli).with_context(|| "Could not build frontend".to_string())?; + + let l = f + .lower() + .with_context(|| "Lowering pass failed".to_string())?; + + let p = Program::try_from(l).with_context(|| { + "Could not lower low level representation to runnable program".to_string() + })?; + + p.run() + .with_context(|| "Program encountered error during execution".to_string())?; + + Ok(()) +} -- GitLab From 2324cf51b1199e69cd06ad83f9d6fb15863f8ac2 Mon Sep 17 00:00:00 2001 From: Petre Tudor Date: Fri, 18 Jul 2025 13:30:01 +0100 Subject: [PATCH 2/2] Port rt-app calibration functionality Port the current rt-app calibration functions and add a 'calibrate' subcommand dedicated to running the calibration step. The reason for adding a subcommand is that running the calibration step in the same process with the workloads makes the emitted traces useless, hence even in the current rt-app there are runs with dummy workloads used only for computing the calibration numbers. The CalibrationData struct stores all the calibration info obtained from the calibration step. This data is written to a JSON file called 'calibration_data.json' (an option to override this path can be added to the calibration subcommand in the future) and can be copied into the "calibration" section of a config file to feed it to the "run" subcommand. See the README.md instructions for running the calibration step. Signed-off-by: Petre Tudor --- tools/rt-app-v2/Cargo.lock | 47 ++++ tools/rt-app-v2/Cargo.toml | 3 + tools/rt-app-v2/README.md | 39 ++- tools/rt-app-v2/examples/config.json | 9 +- tools/rt-app-v2/examples/config_compat.json | 9 +- tools/rt-app-v2/src/lib/affinity_utils.rs | 96 +++++++ tools/rt-app-v2/src/lib/calibration.rs | 252 ++++++++++++++++++ tools/rt-app-v2/src/lib/cli.rs | 18 +- .../rt-app-v2/src/lib/high_level/frontend.rs | 24 +- .../src/lib/high_level/frontend_compat.rs | 2 + .../src/lib/high_level/frontend_v2.rs | 2 + tools/rt-app-v2/src/lib/instr.rs | 9 +- tools/rt-app-v2/src/lib/json_utils.rs | 42 +++ tools/rt-app-v2/src/lib/lib.rs | 3 + tools/rt-app-v2/src/lib/low_level.rs | 15 +- tools/rt-app-v2/src/main.rs | 41 ++- 16 files changed, 568 insertions(+), 43 deletions(-) create mode 100644 tools/rt-app-v2/src/lib/affinity_utils.rs create mode 100644 tools/rt-app-v2/src/lib/calibration.rs create mode 100644 tools/rt-app-v2/src/lib/json_utils.rs diff --git a/tools/rt-app-v2/Cargo.lock b/tools/rt-app-v2/Cargo.lock index f047236d5..25ec2152a 100644 --- a/tools/rt-app-v2/Cargo.lock +++ b/tools/rt-app-v2/Cargo.lock @@ -104,6 +104,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "heck" version = "0.5.0" @@ -122,6 +132,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "libc" +version = "0.2.173" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" + [[package]] name = "memchr" version = "2.7.4" @@ -158,10 +174,19 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "errno", + "libc", "serde", "serde_json", + "strum", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "ryu" version = "1.0.20" @@ -206,6 +231,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.100" diff --git a/tools/rt-app-v2/Cargo.toml b/tools/rt-app-v2/Cargo.toml index bf5d79efe..400e5b4b5 100644 --- a/tools/rt-app-v2/Cargo.toml +++ b/tools/rt-app-v2/Cargo.toml @@ -10,5 +10,8 @@ path = "src/lib/lib.rs" [dependencies] anyhow = "~1.0" clap = { version = "~4.5", features = ["derive"] } +errno = "~0.3" +libc = "~0.2" serde = { version = "~1.0", features = ["derive"] } serde_json = "~1.0" +strum = { version = "~0.27", features = ["derive"] } diff --git a/tools/rt-app-v2/README.md b/tools/rt-app-v2/README.md index ba0847e22..4502c584b 100644 --- a/tools/rt-app-v2/README.md +++ b/tools/rt-app-v2/README.md @@ -62,6 +62,17 @@ This internal representation is lowered to a list of instructions. This represen The Memset event provides an example of such a difference: the low level Memset node holds metadata about the event (the buffer size), but the Memset instruction requires a pointer to a memory region of that size and consequently does not need to track the buffer size separately. +### Calibration + +The original rt-app calibration functionality has been ported to rt-app-v2, with some changes. + +The calibration step can now be run as an independent subcommand. This decision was made because there is not much use for traces generated by a run where the calibration step executed before the rt-app events. Also, when the user wanted to execute only the calibration, with the intention of generating the number for subsequent runs, they would have to also provide some dummy task, otherwise rt-app would complain. + +In consequence, the `calibration` section is mandatory in the JSON config for the v2 frontend. +The original behaviour can be preserved for the compatibility frontend by wrapping the calibration data in an `Option` and executing calibration when we parse `None` from the JSON (not implemented in this PoC). + +Calibration methods are now versioned, which should help with backwards compatibility and repeatability of experiments in the future since we could have as many calibration methods as we want. + ## Getting started To build and run this project, you will need an up to date Rust environment: @@ -70,15 +81,22 @@ If you already have Rust installed, consider updating it by running `rustup upda ## Usage -``` +```bash +$ ./target/debug/rt-app-v2 help Usage: rt-app-v2 Commands: run Selects the v2 frontend run-compat Selects the compatibility frontend + calibrate Runs the calibration step help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help ``` +You can also run ```./target/debug/rt-app-v2 help ``` to learn about all the arguments and options of a subcommand. + ### Running the v2 example Run `cargo build` and then execute the binary: @@ -86,6 +104,7 @@ Run `cargo build` and then execute the binary: ```bash $ cargo build $ ./target/debug/rt-app-v2 examples/config.json +Running with calibration: CalibrationData { cpu: 0, version: V1, pLoad: 139 } Running Memset { count: 10, buf_size: 4194304 } Running Memset { count: 10, buf_size: 2097152 } ``` @@ -96,6 +115,7 @@ or, alternatively: $ cargo run -- examples/config.json Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/rt-app-v2 examples/config.json` +Running with calibration: CalibrationData { cpu: 0, version: V1, pLoad: 139 } Running Memset { count: 10, buf_size: 4194304 } Running Memset { count: 10, buf_size: 2097152 } ``` @@ -107,6 +127,7 @@ Run `cargo build` and then execute the binary: ```bash $ cargo build $ ./target/debug/rt-app-v2 run-compat examples/config_compat.json +Running with calibration: CalibrationData { cpu: 0, version: V1, pLoad: 139 } Running Memset { count: 10, buf_size: 4194304 } Running Memset { count: 10, buf_size: 2097152 } ``` @@ -117,6 +138,7 @@ or, alternatively: $ cargo run -- run-compat examples/config_compat.json Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/rt-app-v2 examples/config.json` +Running with calibration: CalibrationData { cpu: 0, version: V1, pLoad: 139 } Running Memset { count: 10, buf_size: 4194304 } Running Memset { count: 10, buf_size: 2097152 } ``` @@ -127,10 +149,25 @@ For full compatibility with the rt-app command line, the program can be called u $ cargo build $ ln -s target/debug/rt-app-v2 rt-app $ ./rt-app examples/config_compat.json +Running with calibration: CalibrationData { cpu: 0, version: V1, pLoad: 139 } Running Memset { count: 10, buf_size: 4194304 } Running Memset { count: 10, buf_size: 2097152 } ``` +### Running the calibration step + +Both frontends currently expect to find the calibration info in the JSON configuration, so the example configs have been provided with a calibration section already populated. +To run calibration manually and obtain numbers relevant to your platform, invoke the 'calibrate' subcommand: + +```bash +$ ./target/debug/rt-app-v2 calibrate 0 +Calibration step completed: CalibrationData { cpu: 0, version: V1, pLoad: 145 } +``` + +***Note***: The resulting `pLoad` depends not only on the platform, but also the optimization level applied by the compiler, i.e. whether it is a debug or release build. To build in release mode use ```$ cargo build --release``` (the output will be under `target/release/`). + +If the calibration step is successful, a `calibration_data.json` file should have been generated in the project root directory. It contains JSON representation of the generated `CalibrationData`. + ## Roadmap - Implement the existing rt-app workloads. diff --git a/tools/rt-app-v2/examples/config.json b/tools/rt-app-v2/examples/config.json index a824abe36..17d7a4a1f 100644 --- a/tools/rt-app-v2/examples/config.json +++ b/tools/rt-app-v2/examples/config.json @@ -9,5 +9,12 @@ "count": 10, "buf_size": 2097152 } -] +], + +"calibration": { + "type":"CalibrationData", + "cpu":0, + "version":"V1", + "p_load":139 +} } diff --git a/tools/rt-app-v2/examples/config_compat.json b/tools/rt-app-v2/examples/config_compat.json index c45f4e19f..b16536c2f 100644 --- a/tools/rt-app-v2/examples/config_compat.json +++ b/tools/rt-app-v2/examples/config_compat.json @@ -9,5 +9,12 @@ "count": 10, "buf_size": 2097152 } -] +], + +"calibration": { + "type":"CalibrationData", + "cpu":0, + "version":"V1", + "p_load":139 +} } diff --git a/tools/rt-app-v2/src/lib/affinity_utils.rs b/tools/rt-app-v2/src/lib/affinity_utils.rs new file mode 100644 index 000000000..1589bf2fb --- /dev/null +++ b/tools/rt-app-v2/src/lib/affinity_utils.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! Affinity utils +//! +//! Collection of helpers for manipulating a thread's affinity. + +use std::mem; + +use anyhow::{Ok, Result, anyhow}; +use libc::{CPU_SET, CPU_ZERO, cpu_set_t, pid_t, sched_getaffinity, sched_setaffinity}; + +/// Set the affinity mask of a thread to a given cpu_set_t. +/// +/// # Errors +/// +/// This operation can fail if the call to sched_setaffinity() is unsuccessful, in which case the +/// errno value will be returned wrapped inside an anyhow::Error. +/// Consult the man page for sched_setaffinity for all the possible reasons it might fail. +pub fn set_thread_affinity(pid: pid_t, cpu_set: &cpu_set_t) -> Result<()> { + let res = unsafe { sched_setaffinity(pid, mem::size_of::(), cpu_set) }; + + match res { + 0 => Ok(()), + _ => Err(anyhow!( + "sched_setaffinity() failed with error {}", + errno::errno() + )), + } +} + +/// Set the affinity mask of the calling (current) thread to a given cpu_set_t. +pub fn set_calling_thread_affinity(cpu_set: &cpu_set_t) -> Result<()> { + set_thread_affinity(0, cpu_set) +} + +/// Get the affinity mask of a thread. +/// +/// # Errors +/// +/// This operation can fail if the call to sched_getaffinity() is unsuccessful, in which case the +/// errno value will be returned wrapped inside an anyhow::Error. +/// Consult the man page for sched_getaffinity for all the possible reasons it might fail. +pub fn get_thread_affinity(pid: pid_t) -> Result { + let mut s = empty_cpu_set(); + + let res = unsafe { sched_getaffinity(pid, mem::size_of::(), &mut s) }; + + match res { + 0 => Ok(s), + _ => Err(anyhow!( + "sched_getaffinity() failed with error {}", + errno::errno() + )), + } +} + +/// Get the affinity mask of the calling (current) thread. +pub fn get_calling_thread_affinity() -> Result { + get_thread_affinity(0) +} + +fn empty_cpu_set() -> cpu_set_t { + unsafe { + let mut s = mem::zeroed::(); + // Although we just zeroed the memory, the libc documentation recommends we treat + // cpu_set_t as an opaque type, so we call CPU_ZERO to ensure the set is empty. + CPU_ZERO(&mut s); + + s + } +} + +/// Convert a vector of CPU ids to a cpu_set_t. +pub fn cpu_set_from_vec(cpus: Vec) -> cpu_set_t { + let mut s = empty_cpu_set(); + + for cpu in cpus { + unsafe { CPU_SET(cpu, &mut s) }; + } + + s +} diff --git a/tools/rt-app-v2/src/lib/calibration.rs b/tools/rt-app-v2/src/lib/calibration.rs new file mode 100644 index 000000000..211d59c7c --- /dev/null +++ b/tools/rt-app-v2/src/lib/calibration.rs @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +//! Calibration +//! +//! Implements functions for determining the calibration number (p_load) for a target CPU. +//! +//! p_load is the number of ns spent per iteration of a cycle-eater loop, and is meant to represent +//! the highest achievable compute capacity. +//! +//! A cycle-eater is a function whose purpose is to "waste" cycles while stressing the system in a +//! particular way. Picking a cycle-eater that creates a similar kind of load or instruction mix to +//! an event will lead to better calibration numbers for runs using that event. + +use std::{ + cmp, fmt, + hint::black_box, + str::FromStr, + thread::sleep, + time::{Duration, Instant}, +}; + +use anyhow::{Context, Result, anyhow}; +use clap::Parser; +use serde::{Deserialize, Serialize}; +use strum::{EnumString, VariantNames}; + +use crate::affinity_utils::{ + cpu_set_from_vec, get_calling_thread_affinity, set_calling_thread_affinity, +}; + +/// Executes given amount of iterations of a cycle-eater function. +pub fn busyloop(loop_count: u64, f: fn() -> Result) -> Result<()> { + for _ in 0..loop_count { + black_box(f()?); + } + + Ok(()) +} + +/// Recursive cycle-eater based on floating point multiplication and exponentiation. +/// +/// Black boxes are used extensively alongside recursion to prevent aggressive optimization +/// by the compiler. +fn exp2_cycle_eater(x: f64, y: f64, rec: u32) -> f64 { + match rec { + 0 => black_box(x) * black_box(y.exp2()), + _ => black_box(x) * black_box(exp2_cycle_eater(x, y, rec - 1)).exp2(), + } +} + +/// Current rt-app (v1) cycle-eater. +fn rt_app_cycle_eater() -> Result { + let param = 0.95; + let n: f64 = 4.0; + + Ok(exp2_cycle_eater(param, n, 11)) +} + +/// Determine the average ns spent in a cycle-eater iteration. +/// +/// Runs the busyloop function until either the average ns per loop value converges within +/// a 2% difference or it has completed the given iterations. +/// Also offers the option to sleep for a number of seconds between calibration iterations. +/// +/// # Errors +/// +/// This function fails when the average ns per loop value does not converge within the given +/// iteration count. +fn calibrate_cpu_cycles( + sleep_s: u64, + max_iterations: u32, + f: fn() -> Result, +) -> Result { + let mut max_load_loop = 10000; + let mut nsec_per_loop = 0; + let mut avg_ns_per_loop = 0; + + for _ in 0..max_iterations { + if sleep_s > 0 { + sleep(Duration::new(sleep_s, 0)); + } + + // TODO: Allow different clock sources? Instant uses monotonic, maybe allow a real-time + // source as well? + let start_time = Instant::now(); + black_box(busyloop(max_load_loop, black_box(f))?); + let duration_ns = start_time.elapsed().as_nanos(); + + nsec_per_loop = duration_ns / u128::from(max_load_loop); + avg_ns_per_loop = (avg_ns_per_loop + nsec_per_loop) >> 1; + + // Check if the values converge. + if nsec_per_loop.abs_diff(avg_ns_per_loop) * 50 < avg_ns_per_loop { + return Ok(avg_ns_per_loop); + } + + // Randomize the busyloop iteration count in order to avoid hitting some specific platform + // loop duration (e.g. cpufreq period). + max_load_loop += 33333; + max_load_loop %= 1000000; + } + + Err(anyhow!( + "Failed to converge after {} iterations.\n\ + Average nanoseconds per cycle-eater loop on last calibration iteration: {}.\n\ + Average nanoseconds per cycle-eater loop across all calibration iterations: {}", + max_iterations, + nsec_per_loop, + avg_ns_per_loop + )) +} + +/// Current rt-app (v1) calibration method. +/// +/// Mixes two calibration runs (with and without sleep intervals) and chooses the minimum value. +fn calibrate_v1() -> Result { + let calib1 = + calibrate_cpu_cycles(1, 1000, rt_app_cycle_eater).with_context(|| "Method 1 failed")?; + + let calib2 = + calibrate_cpu_cycles(0, 1000, rt_app_cycle_eater).with_context(|| "Method 2 failed")?; + + Ok(cmp::min(calib1, calib2)) +} + +fn calibrate(version: &CalibrationMethod, target_cpu: usize) -> Result { + let original_affinity = get_calling_thread_affinity() + .with_context(|| "Failed to obtain affinity mask for current thread")?; + + let calibration_affinity = cpu_set_from_vec(vec![target_cpu]); + + set_calling_thread_affinity(&calibration_affinity).with_context(|| { + format!( + "Failed to pin calibration task to target cpu {}", + target_cpu + ) + })?; + + let p_load = match version { + CalibrationMethod::V1 => calibrate_v1(), + }; + + set_calling_thread_affinity(&original_affinity) + .with_context(|| "Failed to restore original affinity after calibration step")?; + + p_load +} + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct CalibrationArgs { + /// Target CPU to use during calibration + #[arg(index = 1)] + pub cpu: usize, + + /// Which version of the calibration method to use + #[arg(short, long)] + pub calibration_version: Option, +} + +/// Internal representation for the different versions of calibration methods. +/// +/// The enum variants are parsed from strings with the same name, provided by users via arguments +/// for the calibration subcommand. +#[derive(Default, Debug, Serialize, Deserialize, EnumString, VariantNames)] +#[strum(serialize_all = "snake_case")] +enum CalibrationMethod { + #[default] + V1, +} + +/// Holds information for the calibration run. +/// +/// The value of pload is determined by the calibration step. +/// +/// This struct is serialized/deserialized such that the output of a calibration run can be pasted +/// into the JSON config of a subsequent workload run to provide the calibration data. +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub struct CalibrationData { + cpu: usize, + version: CalibrationMethod, + p_load: u128, +} + +impl CalibrationData { + pub fn new() -> Self { + CalibrationData { + cpu: 0, + version: Default::default(), + p_load: 0, + } + } + + /// Build the CalibrationData from command line arguments and the calibration step. + /// + /// If a version for the calibration method is not supplied, v1 will be selected by default. + /// + /// # Errors + /// + /// This function can fail when the calibration step is unsuccessful. + pub fn build(args: &CalibrationArgs) -> Result { + let v = match &args.calibration_version { + Some(s) => CalibrationMethod::from_str(s).with_context(|| { + format!( + "Unrecognized calibration method '{}'. Supported methods are: {:?}", + s, + CalibrationMethod::VARIANTS + ) + })?, + None => Default::default(), + }; + + let p = calibrate(&v, args.cpu)?; + + Ok(CalibrationData { + cpu: args.cpu, + version: v, + p_load: p, + }) + } +} + +impl Default for CalibrationData { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Display for CalibrationData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CalibrationData") + .field("cpu", &self.cpu) + .field("version", &self.version) + .field("pLoad", &self.p_load) + .finish() + } +} diff --git a/tools/rt-app-v2/src/lib/cli.rs b/tools/rt-app-v2/src/lib/cli.rs index 6dfe290c9..4f78a01df 100644 --- a/tools/rt-app-v2/src/lib/cli.rs +++ b/tools/rt-app-v2/src/lib/cli.rs @@ -20,6 +20,8 @@ //! - v2, which implements the new functionality. //! - compatibility mode, in which rt-app-v2 behaves like the current C Version //! (from command line arguments down to parsing the JSON config). +//! +//! Also implements the 'calibrate' subcommand which runs the cycle calibration step. use std::env; @@ -27,6 +29,7 @@ use anyhow::{Ok, Result, anyhow}; use clap::{Parser, Subcommand}; use crate::{ + calibration::CalibrationArgs, high_level::{ frontend::Frontend, frontend_compat::{FrontendCompat, RunCompatArgs}, @@ -38,7 +41,7 @@ use crate::{ const COMPAT_EXE_NAME: &str = "rt-app"; #[derive(Debug, Subcommand)] -enum CliSub { +pub enum CliSub { /// Selects the v2 frontend. /// /// This is the default if no subcommand is present, unless the program is called under @@ -52,12 +55,16 @@ enum CliSub { /// If the binary is called with the 'rt-app' name (e.g. via symlink), this frontend is /// automatically selected. RunCompat(RunCompatArgs), + /// Runs the calibration step. + /// + /// Runs the calibration step and prints out the derived calibration number. + Calibrate(CalibrationArgs), } #[derive(Parser, Debug)] pub struct Cli { #[command(subcommand)] - subcommand: CliSub, + pub subcommand: CliSub, } impl Cli { @@ -90,7 +97,7 @@ impl Cli { }; let exe_name = exe.file_name().unwrap(); - let subcommand_present = ["run", "run-compat", "help"] + let subcommand_present = ["run", "run-compat", "calibrate", "help"] .iter() .any(|&x| cli_args.contains(&x.to_string())); @@ -129,6 +136,11 @@ impl Frontend for FrontendKind { let c = match args.subcommand { CliSub::Run(a) => FrontendKind::V2(FrontendV2::build(a)?), CliSub::RunCompat(a) => FrontendKind::Compat(FrontendCompat::build(a)?), + CliSub::Calibrate(_) => { + return Err(anyhow!( + "The calibrate subcommand does not have an associated frontend" + )); + } }; Ok(c) diff --git a/tools/rt-app-v2/src/lib/high_level/frontend.rs b/tools/rt-app-v2/src/lib/high_level/frontend.rs index d977626de..0ff136fa9 100644 --- a/tools/rt-app-v2/src/lib/high_level/frontend.rs +++ b/tools/rt-app-v2/src/lib/high_level/frontend.rs @@ -57,20 +57,7 @@ macro_rules! define_config { #[serde(tag = "type")] pub struct Config { pub tasks: Vec<$name>, - } - - impl Config { - pub fn parse_json( - file: &std::path::PathBuf, - ) -> core::result::Result { - let data = std::fs::read_to_string(file) - .with_context(|| format!("Could not read from `{file:?}`"))?; - - let c: Config = - serde_json::from_str(&data).with_context(|| "Failed to parse JSON".to_string())?; - - Ok(c) - } + pub calibration: CalibrationData, } impl TryFrom for LowLevelConfig { @@ -83,6 +70,7 @@ macro_rules! define_config { c.nodes.push(node.try_into()?); } + c.calibration = high_level.calibration; Ok(c) } } @@ -99,7 +87,7 @@ macro_rules! define_frontend { impl Frontend<$cli> for $name { fn build(args: $cli) -> Result { - let c = Config::parse_json(&args.file) + let c = json_utils::parse_json::(&args.file) .with_context(|| "Could not build config from JSON".to_string())?; Ok($name { @@ -113,9 +101,9 @@ macro_rules! define_frontend { .with_context(|| "Could not lower to low level representation".to_string())?; if let Some(json_output) = &self.cli_args.dump_json { - l.write_to_file(json_output).with_context(|| { - "Could not write low level representation to JSON".to_string() - })?; + json_utils::write_to_json::(&l, json_output).with_context( + || "Could not write low level representation to JSON".to_string(), + )?; } Ok(l) diff --git a/tools/rt-app-v2/src/lib/high_level/frontend_compat.rs b/tools/rt-app-v2/src/lib/high_level/frontend_compat.rs index 529c534e8..18e7a420b 100644 --- a/tools/rt-app-v2/src/lib/high_level/frontend_compat.rs +++ b/tools/rt-app-v2/src/lib/high_level/frontend_compat.rs @@ -22,7 +22,9 @@ use serde::{Deserialize, Serialize}; use super::frontend::{Frontend, define_frontend}; use crate::{ + calibration::CalibrationData, high_level::frontend::define_config, + json_utils, low_level::{self, LowLevelConfig, LowLevelNode}, }; diff --git a/tools/rt-app-v2/src/lib/high_level/frontend_v2.rs b/tools/rt-app-v2/src/lib/high_level/frontend_v2.rs index 2aafefb2a..32f93a1ec 100644 --- a/tools/rt-app-v2/src/lib/high_level/frontend_v2.rs +++ b/tools/rt-app-v2/src/lib/high_level/frontend_v2.rs @@ -22,7 +22,9 @@ use serde::{Deserialize, Serialize}; use super::frontend::{Frontend, define_frontend}; use crate::{ + calibration::CalibrationData, high_level::frontend::define_config, + json_utils, low_level::{self, LowLevelConfig, LowLevelNode}, }; diff --git a/tools/rt-app-v2/src/lib/instr.rs b/tools/rt-app-v2/src/lib/instr.rs index 2605ffaa2..a11478295 100644 --- a/tools/rt-app-v2/src/lib/instr.rs +++ b/tools/rt-app-v2/src/lib/instr.rs @@ -23,7 +23,10 @@ use std::{cell::RefCell, cmp, fmt, ptr}; use anyhow::{Ok, Result}; -use crate::low_level::{self, LowLevelConfig, LowLevelNode}; +use crate::{ + calibration::CalibrationData, + low_level::{self, LowLevelConfig, LowLevelNode}, +}; pub trait Run { fn run(&self) -> Result<()>; @@ -176,12 +179,14 @@ define_instr! { #[derive(Debug)] pub struct Program { pub instructions: Vec, + pub calibration: CalibrationData, } impl Program { pub fn new() -> Program { Program { instructions: vec![], + calibration: CalibrationData::new(), } } } @@ -194,6 +199,7 @@ impl Default for Program { impl Run for Program { fn run(&self) -> Result<()> { + println!("Running with calibration: {}", self.calibration); for instr in &self.instructions { instr.run()? } @@ -212,6 +218,7 @@ impl TryFrom for Program { p.instructions.push(node.try_into()?); } + p.calibration = config.calibration; Ok(p) } } diff --git a/tools/rt-app-v2/src/lib/json_utils.rs b/tools/rt-app-v2/src/lib/json_utils.rs new file mode 100644 index 000000000..2f351f28d --- /dev/null +++ b/tools/rt-app-v2/src/lib/json_utils.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright (C) 2025, ARM Limited and contributors. +// +// 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. + +use anyhow::Context; + +pub fn parse_json(file: &std::path::PathBuf) -> core::result::Result +where + for<'a> T: serde::Deserialize<'a>, +{ + let data = + std::fs::read_to_string(file).with_context(|| format!("Could not read from `{file:?}`"))?; + + let t: T = serde_json::from_str(&data).with_context(|| "Failed to parse JSON".to_string())?; + + Ok(t) +} + +pub fn write_to_json( + data: &T, + file: &std::path::PathBuf, +) -> core::result::Result<(), anyhow::Error> +where + T: serde::Serialize, +{ + let json_data = serde_json::to_string(data) + .with_context(|| "Failed to serialize to JSON string".to_string())?; + + std::fs::write(file, json_data).with_context(|| format!("Failed to write to `{file:?}`")) +} diff --git a/tools/rt-app-v2/src/lib/lib.rs b/tools/rt-app-v2/src/lib/lib.rs index 7a740a323..2c4579a74 100644 --- a/tools/rt-app-v2/src/lib/lib.rs +++ b/tools/rt-app-v2/src/lib/lib.rs @@ -14,7 +14,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod affinity_utils; +pub mod calibration; pub mod cli; pub mod high_level; pub mod instr; +pub mod json_utils; pub mod low_level; diff --git a/tools/rt-app-v2/src/lib/low_level.rs b/tools/rt-app-v2/src/lib/low_level.rs index 44bb64543..1036da456 100644 --- a/tools/rt-app-v2/src/lib/low_level.rs +++ b/tools/rt-app-v2/src/lib/low_level.rs @@ -22,9 +22,10 @@ //! This representation is lowered to a program composed of nearly identical instructions which //! can be executed. -use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; +use crate::calibration::CalibrationData; + #[derive(Debug, Serialize, Deserialize)] pub struct Memset { pub count: usize, @@ -46,17 +47,15 @@ pub enum LowLevelNode { #[derive(Debug, Serialize, Deserialize)] pub struct LowLevelConfig { pub nodes: Vec, + pub calibration: CalibrationData, } impl LowLevelConfig { pub fn new() -> LowLevelConfig { - LowLevelConfig { nodes: vec![] } - } - - pub fn write_to_file(&self, file: &std::path::PathBuf) -> Result<()> { - let data = serde_json::to_string(&self) - .with_context(|| "Failed to serialize to JSON string".to_string())?; - std::fs::write(file, data).with_context(|| format!("Failed to write to `{file:?}`")) + LowLevelConfig { + nodes: vec![], + calibration: CalibrationData::new(), + } } } diff --git a/tools/rt-app-v2/src/main.rs b/tools/rt-app-v2/src/main.rs index a082c352e..ea1f5e612 100644 --- a/tools/rt-app-v2/src/main.rs +++ b/tools/rt-app-v2/src/main.rs @@ -14,28 +14,49 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::str::FromStr; + use anyhow::{Context, Ok, Result}; use lib::{ - cli::{Cli, FrontendKind}, + calibration::CalibrationData, + cli::{Cli, CliSub, FrontendKind}, high_level::frontend::Frontend, instr::{Program, Run}, + json_utils, }; fn main() -> Result<()> { let cli = Cli::build_cli_args().with_context(|| "Could not build command line".to_string())?; - let f = FrontendKind::build(cli).with_context(|| "Could not build frontend".to_string())?; + match cli.subcommand { + CliSub::Calibrate(a) => { + let cal = + CalibrationData::build(&a).with_context(|| "Calibration step failed".to_string())?; + + println!("Calibration step completed: {cal}"); + + let f = std::path::PathBuf::from_str("calibration_data.json") + .with_context(|| "Could not parse string to file path".to_string())?; + + json_utils::write_to_json(&cal, &f) + .with_context(|| "Could not write calibration data to JSON".to_string())?; + } + _ => { + let f = + FrontendKind::build(cli).with_context(|| "Could not build frontend".to_string())?; - let l = f - .lower() - .with_context(|| "Lowering pass failed".to_string())?; + let l = f + .lower() + .with_context(|| "Lowering pass failed".to_string())?; - let p = Program::try_from(l).with_context(|| { - "Could not lower low level representation to runnable program".to_string() - })?; + let p = Program::try_from(l).with_context(|| { + "Could not lower low level representation to runnable program".to_string() + })?; - p.run() - .with_context(|| "Program encountered error during execution".to_string())?; + p.run() + .with_context(|| "Program encountered error during execution".to_string())?; + } + }; Ok(()) } -- GitLab