Compare commits
12 Commits
f988cc0d58
...
eb53e8190a
| Author | SHA1 | Date | |
|---|---|---|---|
| eb53e8190a | |||
| 6d13dee2a6 | |||
| a43cca122d | |||
| fc0c1e4576 | |||
| 83f47db352 | |||
| f877419c43 | |||
| ac0712e8b9 | |||
| d097261be3 | |||
| 8ce2ec6b21 | |||
| 5e68844868 | |||
| 217ca485b7 | |||
| 5b42305568 |
+115
@@ -0,0 +1,115 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "pam"
|
||||
version = "0.2.5"
|
||||
dependencies = [
|
||||
"redbear-login-protocol",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redbear-login-protocol"
|
||||
version = "0.2.5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
@@ -33,6 +33,7 @@ cmake "${COOKBOOK_SOURCE}" \
|
||||
-DBUILD_EXAMPLES=OFF \
|
||||
-DFEATURE_sensorfw=OFF \
|
||||
-DQT_GENERATE_SBOM=OFF \
|
||||
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
|
||||
-Wno-dev
|
||||
|
||||
cmake --build . -j${COOKBOOK_MAKE_JOBS}
|
||||
|
||||
@@ -51,6 +51,7 @@ if [ ! -d "${HOST_BUILD}/lib/cmake/Qt6Svg" ]; then
|
||||
-DQT_BUILD_EXAMPLES=OFF \
|
||||
-DQT_BUILD_TESTS=OFF \
|
||||
-DQT_GENERATE_SBOM=OFF \
|
||||
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
|
||||
-Wno-dev
|
||||
cmake --build "${HOST_QTSVG_BUILD}" -j"${COOKBOOK_MAKE_JOBS}"
|
||||
cmake --install "${HOST_QTSVG_BUILD}" --prefix "${HOST_BUILD}"
|
||||
@@ -86,6 +87,7 @@ if [ ! -f "${HOST_BUILD}/bin/qmlcachegen" ] || [ ! -f "${HOST_BUILD}/bin/qmlaots
|
||||
-DQT_BUILD_EXAMPLES=OFF \
|
||||
-DQT_BUILD_TESTS=OFF \
|
||||
-DQT_GENERATE_SBOM=OFF \
|
||||
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
|
||||
-Wno-dev
|
||||
cmake --build "${DECL_HOST}" --target qmllint qmlimportscanner qmltyperegistrar qmlaotstats svgtoqml -j"${COOKBOOK_MAKE_JOBS}" || true
|
||||
# Generate jsroot.qmltypes needed by qmlcachegen using the host-built qmltyperegistrar.
|
||||
@@ -232,6 +234,7 @@ cmake "${COOKBOOK_SOURCE}" \
|
||||
-DQT_BUILD_EXAMPLES=OFF \
|
||||
-DQT_BUILD_TESTS=OFF \
|
||||
-DQT_GENERATE_SBOM=OFF \
|
||||
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
|
||||
-DQT_FEATURE_qml_jit=OFF \
|
||||
-DQT_FEATURE_ssl=OFF \
|
||||
-DQT_FEATURE_network=OFF \
|
||||
|
||||
@@ -16,16 +16,231 @@ script = """
|
||||
DYNAMIC_INIT
|
||||
|
||||
HOST_BUILD="${COOKBOOK_ROOT}/build/qt-host-build"
|
||||
HOST_QST_BUILD="${COOKBOOK_ROOT}/build/qtshadertools-host-build"
|
||||
HOST_QST_STAMP="${HOST_BUILD}/.redbear-host-qsb-6.11.1"
|
||||
HOST_PATH="/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl"
|
||||
|
||||
source "${COOKBOOK_ROOT}/local/scripts/lib/qt-sysroot.sh"
|
||||
|
||||
redbear_qt_link_sysroot_dirs "${COOKBOOK_SYSROOT}" plugins mkspecs metatypes modules
|
||||
|
||||
# Ensure the strtold compat library is available for linking
|
||||
if [ -f "${COOKBOOK_SYSROOT}/lib/libredbear-qt-strtold-compat.so" ]; then
|
||||
mkdir -p "${COOKBOOK_SYSROOT}/usr/lib"
|
||||
cp -f "${COOKBOOK_SYSROOT}/lib/libredbear-qt-strtold-compat.so" "${COOKBOOK_SYSROOT}/usr/lib/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Step 1: Build qtshadertools host tools (qsb) on the host
|
||||
# ============================================================
|
||||
if [ ! -f "${HOST_BUILD}/bin/qsb" ] || [ ! -f "${HOST_QST_STAMP}" ]; then
|
||||
echo "=== Building qtshadertools host tools (qsb) ==="
|
||||
rm -rf "${HOST_QST_BUILD}"
|
||||
env -i \
|
||||
HOME="${HOME}" \
|
||||
PATH="${HOST_PATH}" \
|
||||
cmake -S "${COOKBOOK_SOURCE}" -B "${HOST_QST_BUILD}" \
|
||||
-GNinja \
|
||||
-DCMAKE_C_COMPILER=/usr/bin/cc \
|
||||
-DCMAKE_CXX_COMPILER=/usr/bin/c++ \
|
||||
-DCMAKE_ASM_COMPILER=/usr/bin/cc \
|
||||
-DCMAKE_AR=/usr/bin/ar \
|
||||
-DCMAKE_RANLIB=/usr/bin/ranlib \
|
||||
-DCMAKE_STRIP=/usr/bin/strip \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH="${HOST_BUILD}" \
|
||||
-DQT_HOST_PATH="${HOST_BUILD}" \
|
||||
-DQT_BUILD_TOOLS_BY_DEFAULT=ON \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DBUILD_EXAMPLES=OFF \
|
||||
-Wno-dev
|
||||
(
|
||||
cd "${HOST_QST_BUILD}"
|
||||
env -i HOME="${HOME}" PATH="${HOST_PATH}" cmake --build . -j"${COOKBOOK_MAKE_JOBS}"
|
||||
)
|
||||
(
|
||||
cd "${HOST_QST_BUILD}"
|
||||
env -i HOME="${HOME}" PATH="${HOST_PATH}" cmake --install . --prefix "${HOST_BUILD}"
|
||||
)
|
||||
printf '%s\n' "6.11.1" > "${HOST_QST_STAMP}"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Step 1b: Generate Qt6ShaderToolsTools cmake package
|
||||
# Qt6's standalone module build does not generate this tools wrapper.
|
||||
# Cross-compile needs find_package(Qt6ShaderToolsTools) -> Qt6::qsb.
|
||||
# Pattern follows Qt6WaylandScannerTools from qtbase host build.
|
||||
# ============================================================
|
||||
QST_CMAKE_DIR="${HOST_BUILD}/lib/cmake/Qt6ShaderToolsTools"
|
||||
if [ ! -f "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfig.cmake" ]; then
|
||||
mkdir -p "${QST_CMAKE_DIR}"
|
||||
QSB_PATH="${HOST_BUILD}/bin/qsb"
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfig.cmake" << 'EOF_CFG'
|
||||
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
|
||||
macro(set_and_check _var _file)
|
||||
set(${_var} "${_file}")
|
||||
if(NOT EXISTS "${_file}")
|
||||
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
|
||||
endif()
|
||||
endmacro()
|
||||
macro(check_required_components _NAME)
|
||||
foreach(comp ${${_NAME}_FIND_COMPONENTS})
|
||||
if(NOT ${_NAME}_${comp}_FOUND)
|
||||
if(${_NAME}_FIND_REQUIRED_${comp})
|
||||
set(${_NAME}_FOUND FALSE)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endmacro()
|
||||
cmake_minimum_required(VERSION 3.16...3.21)
|
||||
include(CMakeFindDependencyMacro)
|
||||
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsDependencies.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsDependencies.cmake")
|
||||
endif()
|
||||
if(NOT DEFINED "Qt6ShaderToolsTools_FOUND")
|
||||
set("Qt6ShaderToolsTools_FOUND" TRUE)
|
||||
endif()
|
||||
set(__qt_ShaderToolsTools_should_include_targets_code "TRUE")
|
||||
if(__qt_ShaderToolsTools_should_include_targets_code)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsTargetsPrecheck.cmake")
|
||||
endif()
|
||||
if(NOT __qt_ShaderToolsTools_skip_include_targets_file
|
||||
AND Qt6ShaderToolsTools_FOUND
|
||||
AND __qt_ShaderToolsTools_should_include_targets_code)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsTargets.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsAdditionalTargetInfo.cmake")
|
||||
if(NOT QT_NO_CREATE_VERSIONLESS_TARGETS)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsVersionlessTargets.cmake")
|
||||
endif()
|
||||
set(__qt_ShaderToolsTools_targets_file_included ON)
|
||||
endif()
|
||||
if(__qt_ShaderToolsTools_targets_file_included AND Qt6ShaderToolsTools_FOUND)
|
||||
__qt_internal_promote_target_to_global(Qt6::qsb)
|
||||
endif()
|
||||
set(Qt6ShaderToolsTools_TARGETS "Qt6::qsb")
|
||||
EOF_CFG
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsTargets.cmake" << 'EOF_TGT'
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(VERSION 2.8.3...4.1)
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
set(_targets_defined "")
|
||||
set(_targets_not_defined "")
|
||||
set(_expected_targets "")
|
||||
foreach(_expected IN ITEMS Qt6::qsb)
|
||||
list(APPEND _expected_targets "${_expected}")
|
||||
if(TARGET "${_expected}")
|
||||
list(APPEND _targets_defined "${_expected}")
|
||||
else()
|
||||
list(APPEND _targets_not_defined "${_expected}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_expected)
|
||||
if(_targets_defined STREQUAL _expected_targets)
|
||||
cmake_policy(POP)
|
||||
return()
|
||||
endif()
|
||||
unset(_targets_defined)
|
||||
unset(_targets_not_defined)
|
||||
unset(_expected_targets)
|
||||
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
if(_IMPORT_PREFIX STREQUAL "/")
|
||||
set(_IMPORT_PREFIX "")
|
||||
endif()
|
||||
add_executable(Qt6::qsb IMPORTED)
|
||||
set_target_properties(Qt6::qsb PROPERTIES
|
||||
COMPATIBLE_INTERFACE_STRING "QT_MAJOR_VERSION"
|
||||
INTERFACE_QT_MAJOR_VERSION "6"
|
||||
)
|
||||
file(GLOB _cfg_files "${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsTargets-*.cmake")
|
||||
foreach(_cfg IN LISTS _cfg_files)
|
||||
include("${_cfg}")
|
||||
endforeach()
|
||||
unset(_cfg)
|
||||
unset(_cfg_files)
|
||||
unset(_IMPORT_PREFIX)
|
||||
unset(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
EOF_TGT
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsTargets-release.cmake" << EOF_REL
|
||||
set_property(TARGET Qt6::qsb APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(Qt6::qsb PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${QSB_PATH}"
|
||||
)
|
||||
set(_cmake_import_check_targets "Qt6::qsb")
|
||||
set(_cmake_import_check_files_for_Qt6::qsb "${QSB_PATH}")
|
||||
EOF_REL
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsTargetsPrecheck.cmake" << 'EOF_PRE'
|
||||
_qt_internal_should_include_targets(
|
||||
TARGETS qsb
|
||||
NAMESPACE Qt6::
|
||||
OUT_VAR_SHOULD_SKIP __qt_ShaderToolsTools_skip_include_targets_file
|
||||
)
|
||||
EOF_PRE
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsAdditionalTargetInfo.cmake" << 'EOF_ADD'
|
||||
if(NOT DEFINED QT_DEFAULT_IMPORT_CONFIGURATION)
|
||||
set(QT_DEFAULT_IMPORT_CONFIGURATION RELEASE)
|
||||
endif()
|
||||
get_target_property(_qt_loc Qt6::qsb IMPORTED_LOCATION_RELEASE)
|
||||
get_target_property(_qt_loc_def Qt6::qsb IMPORTED_LOCATION_${QT_DEFAULT_IMPORT_CONFIGURATION})
|
||||
set_property(TARGET Qt6::qsb APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
if(_qt_loc)
|
||||
set_property(TARGET Qt6::qsb PROPERTY IMPORTED_LOCATION_RELWITHDEBINFO "${_qt_loc}")
|
||||
endif()
|
||||
set_property(TARGET Qt6::qsb APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
if(_qt_loc)
|
||||
set_property(TARGET Qt6::qsb PROPERTY IMPORTED_LOCATION_MINSIZEREL "${_qt_loc}")
|
||||
endif()
|
||||
if(_qt_loc_def)
|
||||
set_property(TARGET Qt6::qsb PROPERTY IMPORTED_LOCATION "${_qt_loc_def}")
|
||||
endif()
|
||||
EOF_ADD
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsVersionlessTargets.cmake" << 'EOF_VER'
|
||||
foreach(__qt_tool qsb)
|
||||
if(NOT TARGET Qt::${__qt_tool} AND TARGET Qt6::${__qt_tool})
|
||||
add_executable(Qt::${__qt_tool} IMPORTED GLOBAL)
|
||||
foreach(__qt_cfg IMPORTED_LOCATION IMPORTED_LOCATION_RELEASE IMPORTED_LOCATION_RELWITHDEBINFO IMPORTED_LOCATION_MINSIZEREL IMPORTED_LOCATION_DEBUG)
|
||||
get_target_property(__qt_loc Qt6::${__qt_tool} ${__qt_cfg})
|
||||
if(__qt_loc AND EXISTS "${__qt_loc}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
set_target_properties(Qt::${__qt_tool} PROPERTIES IMPORTED_LOCATION "${__qt_loc}")
|
||||
endif()
|
||||
endforeach()
|
||||
EOF_VER
|
||||
|
||||
printf 'set(Qt6ShaderToolsTools_FOUND TRUE)\n' \
|
||||
> "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsDependencies.cmake"
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfigVersionImpl.cmake" << 'EOF_VIMPL'
|
||||
set(PACKAGE_VERSION "6.11.1")
|
||||
if(PACKAGE_FIND_VERSION VERSION_LESS_EQUAL PACKAGE_VERSION)
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
endif()
|
||||
if(NOT PACKAGE_VERSION_COMPATIBLE)
|
||||
set(PACKAGE_VERSION_UNSUITABLE TRUE)
|
||||
endif()
|
||||
EOF_VIMPL
|
||||
|
||||
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfigVersion.cmake" << 'EOF_VER2'
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsConfigVersionImpl.cmake")
|
||||
EOF_VER2
|
||||
|
||||
echo "Generated Qt6ShaderToolsTools cmake package"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Step 2: Cross-compile qtshadertools for Redox
|
||||
# ============================================================
|
||||
redbear_qt_reset_cmake_cache_dir
|
||||
|
||||
cmake "${COOKBOOK_SOURCE}" \
|
||||
@@ -34,7 +249,9 @@ cmake "${COOKBOOK_SOURCE}" \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \
|
||||
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
|
||||
-DQT_BUILD_TOOLS_BY_DEFAULT=OFF \
|
||||
-DQT_GENERATE_SBOM=OFF \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DBUILD_EXAMPLES=OFF \
|
||||
@@ -61,7 +278,20 @@ PY
|
||||
|
||||
cmake --install . --prefix "${COOKBOOK_STAGE}/usr"
|
||||
|
||||
# Supplemental: copy libraries and plugins to ensure symlinks present
|
||||
cp -f "${COOKBOOK_SOURCE}/tools/qsb/Qt6ShaderToolsMacros.cmake" \
|
||||
"${COOKBOOK_STAGE}/usr/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsMacros.cmake" 2>/dev/null || true
|
||||
cp -f "${COOKBOOK_SOURCE}/tools/qsb/Qt6ShaderToolsMacros.cmake" \
|
||||
"${HOST_BUILD}/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsMacros.cmake" 2>/dev/null || true
|
||||
|
||||
QST_CFG="${COOKBOOK_STAGE}/usr/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsConfig.cmake"
|
||||
if [ -f "${QST_CFG}" ] && ! grep -q "ShaderToolsMacros" "${QST_CFG}"; then
|
||||
sed -i 's/foreach(extra_cmake_include )/foreach(extra_cmake_include Qt6ShaderToolsMacros.cmake)/' "${QST_CFG}"
|
||||
fi
|
||||
QST_CFG_HOST="${HOST_BUILD}/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsConfig.cmake"
|
||||
if [ -f "${QST_CFG_HOST}" ] && ! grep -q "ShaderToolsMacros" "${QST_CFG_HOST}"; then
|
||||
sed -i 's/foreach(extra_cmake_include )/foreach(extra_cmake_include Qt6ShaderToolsMacros.cmake)/' "${QST_CFG_HOST}"
|
||||
fi
|
||||
|
||||
for lib in lib/libQt6*.so*; do
|
||||
[ -f "${lib}" ] && cp -an "${lib}" "${COOKBOOK_STAGE}/usr/lib/"
|
||||
done
|
||||
|
||||
@@ -97,6 +97,8 @@ cmake "${COOKBOOK_SOURCE}" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-lc -lffi" \
|
||||
-DCMAKE_C_STANDARD_LIBRARIES="-lffi" \
|
||||
-DCMAKE_CXX_STANDARD_LIBRARIES="-lffi" \
|
||||
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
|
||||
-DQT_GENERATE_SBOM=OFF \
|
||||
-Wno-dev
|
||||
|
||||
cmake --build . -j"${COOKBOOK_MAKE_JOBS}"
|
||||
|
||||
@@ -107,6 +107,15 @@ pub fn rapl_power_watts(
|
||||
(watts, curr)
|
||||
}
|
||||
|
||||
/// Read a RAPL energy counter for an arbitrary domain MSR (PP0 core,
|
||||
/// PP1 uncore, DRAM). Same energy-unit conversion as package domain.
|
||||
/// Returns `None` when the MSR is unavailable.
|
||||
pub fn read_rapl_domain_energy(msr: u32) -> Option<(u64, Instant)> {
|
||||
let unit = crate::msr::RaplUnit::read(0)?;
|
||||
let raw = crate::msr::read_rapl_energy(0, msr)?;
|
||||
Some((unit.energy_to_uj(raw), Instant::now()))
|
||||
}
|
||||
|
||||
pub fn detect_cpus() -> Vec<u32> {
|
||||
// Redox exposes the CPU count via the sys:cpu scheme file
|
||||
// (kernel/src/scheme/sys/cpu.rs) as "CPUs: N\n...". /dev/cpu/ does
|
||||
|
||||
@@ -79,6 +79,7 @@ pub struct CpuRow {
|
||||
pub prev_load: (u64, u64),
|
||||
pub load_history: VecDeque<u8>,
|
||||
pub core_type: CoreType,
|
||||
pub hwp: Option<crate::msr::HwpInfo>,
|
||||
}
|
||||
|
||||
impl CpuRow {
|
||||
@@ -138,6 +139,10 @@ pub meminfo: crate::meminfo::MemInfo,
|
||||
pub prev_refresh_secs: f64,
|
||||
pub process_sort: crate::process::SortMode,
|
||||
pub process_filter: String,
|
||||
/// Scheduler statistics from `/scheme/sys/stat` (Redox) or
|
||||
/// `/proc/stat` (Linux). Read every tick — it's a single
|
||||
/// file read. Fields degrade to `None` when unavailable.
|
||||
pub sched_stats: crate::sched::SchedStats,
|
||||
/// When true, render the Process tab as a tree (parents above
|
||||
/// children, prefixed with `├─ ` / `└─ ` connector characters).
|
||||
/// Toggled by the `T` hotkey. Sort modes are honored within
|
||||
@@ -155,6 +160,12 @@ pub meminfo: crate::meminfo::MemInfo,
|
||||
rapl_prev: Option<(u64, std::time::Instant)>,
|
||||
/// Why RAPL package power is unavailable (empty if working).
|
||||
pub rapl_status: String,
|
||||
pub pp0_power_w: Option<f64>,
|
||||
rapl_pp0_prev: Option<(u64, std::time::Instant)>,
|
||||
pub pp1_power_w: Option<f64>,
|
||||
rapl_pp1_prev: Option<(u64, std::time::Instant)>,
|
||||
pub dram_power_w: Option<f64>,
|
||||
rapl_dram_prev: Option<(u64, std::time::Instant)>,
|
||||
/// Per-PID IO rate history (normalized KiB/s samples,
|
||||
/// 0..=255 against the per-history max). Used by the
|
||||
/// Process tab to render a small sparkline per process.
|
||||
@@ -229,6 +240,7 @@ pub meminfo: crate::meminfo::MemInfo,
|
||||
pub interval_input: Option<String>,
|
||||
pub current_tab: TabId,
|
||||
pub bench_start_time: Option<Instant>,
|
||||
pub collector_stats: crate::collector::CollectorStats,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -325,6 +337,7 @@ impl App {
|
||||
prev_load: (0, 0),
|
||||
load_history: VecDeque::with_capacity(LOAD_HISTORY_LEN),
|
||||
core_type: type_for(id),
|
||||
hwp: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -393,11 +406,18 @@ impl App {
|
||||
prev_refresh_secs: 0.0,
|
||||
process_sort: crate::process::SortMode::default(),
|
||||
process_filter: String::new(),
|
||||
sched_stats: crate::sched::SchedStats::default(),
|
||||
process_tree: false,
|
||||
folded: std::collections::BTreeSet::new(),
|
||||
pkg_power_w: None,
|
||||
rapl_prev: None,
|
||||
rapl_status: "probing...".into(),
|
||||
pp0_power_w: None,
|
||||
rapl_pp0_prev: None,
|
||||
pp1_power_w: None,
|
||||
rapl_pp1_prev: None,
|
||||
dram_power_w: None,
|
||||
rapl_dram_prev: None,
|
||||
io_history: std::collections::BTreeMap::new(),
|
||||
last_clicked_cpu: None,
|
||||
sort_ascending: false,
|
||||
@@ -416,6 +436,7 @@ impl App {
|
||||
max_history_pids: 500,
|
||||
pid_last_seen: std::collections::BTreeMap::new(),
|
||||
refresh_tick: 0,
|
||||
collector_stats: crate::collector::CollectorStats::default(),
|
||||
};
|
||||
// v1.40: load persisted session state and apply.
|
||||
// Missing or malformed session falls back to the
|
||||
@@ -605,7 +626,17 @@ impl App {
|
||||
self.prev_refresh_secs = now_secs;
|
||||
}
|
||||
|
||||
for row in &mut self.cpus {
|
||||
// Scheduler stats (context switches, IRQs). One file read
|
||||
// — cheap enough for every tick.
|
||||
self.sched_stats = crate::sched::SchedStats::read();
|
||||
|
||||
let pkg_temps: Vec<Option<u32>> = self
|
||||
.cpus
|
||||
.iter()
|
||||
.map(|row| self.sensors.pkg_temp_c(row.id))
|
||||
.collect();
|
||||
|
||||
self.collector_stats = crate::collector::collect(&mut self.cpus, |i, row| {
|
||||
if let Some(status) = read_thermal_status(row.id) {
|
||||
row.temp_c = if status & THERM_STATUS_READOUT_VALID != 0 {
|
||||
Some(((status & THERM_STATUS_TEMP_MASK) >> 16) as u32)
|
||||
@@ -616,11 +647,7 @@ impl App {
|
||||
row.critical = status & THERM_STATUS_CRITICAL != 0;
|
||||
row.power_limit = status & THERM_STATUS_POWER_LIMIT != 0;
|
||||
} else {
|
||||
// IA32_THERM_STATUS is Intel-only. On AMD, fall back to
|
||||
// k10temp Tctl (the package control temperature), which
|
||||
// applies to all CPUs on the same package. This is the
|
||||
// canonical hwmon-based CPU temperature for Zen and later.
|
||||
row.temp_c = self.sensors.pkg_temp_c(row.id);
|
||||
row.temp_c = pkg_temps[i];
|
||||
row.prochot = false;
|
||||
row.critical = false;
|
||||
row.power_limit = false;
|
||||
@@ -628,16 +655,13 @@ impl App {
|
||||
if let Some(ctl) = read_current_perf_ctl(row.id) {
|
||||
let state = ((ctl & PERF_CTL_STATE_MASK) >> 8) as u8;
|
||||
row.current_idx = row.pstates.iter().position(|p| ((p.ctl & PERF_CTL_STATE_MASK) >> 8) as u8 == state);
|
||||
let cur = row.current_idx.and_then(|i| row.pstates.get(i));
|
||||
let cur = row.current_idx.and_then(|idx| row.pstates.get(idx));
|
||||
row.freq_khz = cur.map(|p| p.freq_khz).unwrap_or(0);
|
||||
row.current_power_mw = cur.map(|p| p.power_mw);
|
||||
} else {
|
||||
// MSR unavailable — try sysfs cpufreq (Linux fallback, like htop).
|
||||
row.current_idx = None;
|
||||
row.freq_khz = read_cpu_freq_khz_sysfs(row.id).unwrap_or(0);
|
||||
row.current_power_mw = None;
|
||||
// Match current frequency against synthetic P-state table
|
||||
// to determine which P-state we're in (intel_pstate fallback).
|
||||
if row.freq_khz > 0 && !row.pstates.is_empty() {
|
||||
row.current_idx = row
|
||||
.pstates
|
||||
@@ -646,7 +670,7 @@ impl App {
|
||||
.min_by_key(|(_, p)| {
|
||||
(p.freq_khz as i64 - row.freq_khz as i64).unsigned_abs()
|
||||
})
|
||||
.map(|(i, _)| i);
|
||||
.map(|(idx, _)| idx);
|
||||
}
|
||||
}
|
||||
row.load_pct = read_load(row.id, &mut row.prev_load) * 100.0;
|
||||
@@ -655,7 +679,8 @@ impl App {
|
||||
}
|
||||
row.load_history
|
||||
.push_back(row.load_pct.clamp(0.0, 100.0) as u8);
|
||||
}
|
||||
row.hwp = crate::msr::HwpInfo::read(row.id);
|
||||
});
|
||||
// Read package power from RAPL powercap (Intel/AMD). Requires
|
||||
// kernel CONFIG_POWERCAP and intel_rapl/amd_energy driver.
|
||||
// Falls back to MSR-based power or sysfs power values.
|
||||
@@ -679,6 +704,36 @@ impl App {
|
||||
// No MSR device at all — RAPL unsupported (QEMU, old CPU).
|
||||
self.rapl_status = "n/a (unsupported)".into();
|
||||
}
|
||||
if let Some(curr) = crate::acpi::read_rapl_domain_energy(crate::msr::MSR_PP0_ENERGY_STATUS) {
|
||||
match self.rapl_pp0_prev {
|
||||
Some(prev) => {
|
||||
let (w, next) = crate::acpi::rapl_power_watts(curr, prev);
|
||||
self.rapl_pp0_prev = Some(next);
|
||||
if w > 0.0 { self.pp0_power_w = Some(w); }
|
||||
}
|
||||
None => self.rapl_pp0_prev = Some(curr),
|
||||
}
|
||||
}
|
||||
if let Some(curr) = crate::acpi::read_rapl_domain_energy(crate::msr::MSR_PP1_ENERGY_STATUS) {
|
||||
match self.rapl_pp1_prev {
|
||||
Some(prev) => {
|
||||
let (w, next) = crate::acpi::rapl_power_watts(curr, prev);
|
||||
self.rapl_pp1_prev = Some(next);
|
||||
if w > 0.0 { self.pp1_power_w = Some(w); }
|
||||
}
|
||||
None => self.rapl_pp1_prev = Some(curr),
|
||||
}
|
||||
}
|
||||
if let Some(curr) = crate::acpi::read_rapl_domain_energy(crate::msr::MSR_DRAM_ENERGY_STATUS) {
|
||||
match self.rapl_dram_prev {
|
||||
Some(prev) => {
|
||||
let (w, next) = crate::acpi::rapl_power_watts(curr, prev);
|
||||
self.rapl_dram_prev = Some(next);
|
||||
if w > 0.0 { self.dram_power_w = Some(w); }
|
||||
}
|
||||
None => self.rapl_dram_prev = Some(curr),
|
||||
}
|
||||
}
|
||||
// Re-read cpufreq state (catches external governor changes).
|
||||
self.cpufreq.refresh();
|
||||
if let Some(pkg) = read_package_thermal_status(self.cpus[0].id) {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
//! Multi-threaded per-CPU data collector.
|
||||
//!
|
||||
//! Spawns one worker thread per CPU using `std::thread::scope` (which
|
||||
//! exercises `pthread_create` via relibc). Workers are barrier-synchronised
|
||||
//! (`std::sync::Barrier` → futex wake/wait) and attempt CPU pinning
|
||||
//! (`sched_setaffinity` via the kernel proc scheme). Thread names are
|
||||
//! set via `pthread_setname_np` (through `Builder::name`).
|
||||
//!
|
||||
//! This module exists to visibly demonstrate Red Bear OS Phase 0
|
||||
//! threading infrastructure: per-CPU scheduler placement, futex
|
||||
//! sharding, and the POSIX scheduling API surface.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Barrier;
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CollectorStats {
|
||||
pub thread_count: usize,
|
||||
pub pinned_count: usize,
|
||||
pub elapsed_us: u64,
|
||||
pub barrier_size: usize,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
unsafe extern "C" {
|
||||
fn sched_setaffinity(pid: i32, cpusetsize: usize, mask: *const u64) -> i32;
|
||||
}
|
||||
|
||||
fn try_pin_cpu(cpu_id: u32) -> bool {
|
||||
if cpu_id >= 64 {
|
||||
return false;
|
||||
}
|
||||
let mask: u64 = 1u64 << cpu_id;
|
||||
let bytes = mask.to_le_bytes();
|
||||
if std::fs::write("/proc/self/sched-affinity", bytes).is_ok() {
|
||||
return true;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
unsafe {
|
||||
sched_setaffinity(0, std::mem::size_of::<u64>(), &mask) == 0
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect<T, F>(items: &mut [T], work: F) -> CollectorStats
|
||||
where
|
||||
F: Fn(usize, &mut T) + Sync,
|
||||
T: Send,
|
||||
{
|
||||
let n = items.len();
|
||||
if n <= 1 {
|
||||
if let Some((i, item)) = items.iter_mut().enumerate().next() {
|
||||
work(i, item);
|
||||
}
|
||||
return CollectorStats {
|
||||
thread_count: if n == 1 { 1 } else { 0 },
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
let barrier = Barrier::new(n);
|
||||
let pinned = AtomicUsize::new(0);
|
||||
let spawned = AtomicUsize::new(0);
|
||||
let start = Instant::now();
|
||||
|
||||
thread::scope(|s| {
|
||||
for (i, item) in items.iter_mut().enumerate() {
|
||||
let barrier = &barrier;
|
||||
let work = &work;
|
||||
let pinned = &pinned;
|
||||
let spawned = &spawned;
|
||||
|
||||
s.spawn(move || {
|
||||
spawned.fetch_add(1, Ordering::Relaxed);
|
||||
if try_pin_cpu(i as u32) {
|
||||
pinned.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
barrier.wait();
|
||||
work(i, item);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
CollectorStats {
|
||||
thread_count: spawned.load(Ordering::Relaxed),
|
||||
pinned_count: pinned.load(Ordering::Relaxed),
|
||||
elapsed_us: start.elapsed().as_micros() as u64,
|
||||
barrier_size: n,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn single_item_runs_inline() {
|
||||
let mut data = [0u64; 1];
|
||||
let stats = collect(&mut data, |i, v| {
|
||||
*v = i as u64 + 1;
|
||||
});
|
||||
assert_eq!(data[0], 1);
|
||||
assert_eq!(stats.thread_count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_items_updated() {
|
||||
let mut data = vec![0u64; 8];
|
||||
let stats = collect(&mut data, |i, v| {
|
||||
*v = (i * i) as u64;
|
||||
});
|
||||
for (i, v) in data.iter().enumerate() {
|
||||
assert_eq!(*v, (i * i) as u64, "item {i} not updated");
|
||||
}
|
||||
assert!(stats.thread_count >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_slice_is_safe() {
|
||||
let mut data: Vec<u64> = vec![];
|
||||
let stats = collect(&mut data, |_, _| {});
|
||||
assert_eq!(stats.thread_count, 0);
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ mod acpi;
|
||||
mod app;
|
||||
mod battery;
|
||||
mod bench;
|
||||
mod collector;
|
||||
mod config;
|
||||
mod cpufreq;
|
||||
mod cpuid;
|
||||
@@ -59,6 +60,7 @@ mod pid_detail;
|
||||
mod platform;
|
||||
mod process;
|
||||
mod render;
|
||||
mod sched;
|
||||
mod sensor;
|
||||
mod session;
|
||||
mod smart;
|
||||
@@ -344,7 +346,7 @@ fn main() -> io::Result<()> {
|
||||
match app.current_tab {
|
||||
TabId::PerCpu => {
|
||||
f.render_stateful_widget(
|
||||
render_cpu_table(&app.cpus, app.expanded_cpu, focused_panel == 1, app.pkg_power_w, &app.rapl_status),
|
||||
render_cpu_table(&app.cpus, app.expanded_cpu, focused_panel == 1, app.pkg_power_w, &app.rapl_status, &app.sched_stats.per_cpu_irqs),
|
||||
body_area,
|
||||
&mut app.table_state,
|
||||
);
|
||||
|
||||
@@ -280,4 +280,67 @@ impl PackageThermal {
|
||||
if self.hfi { parts.push("HFI"); }
|
||||
if parts.is_empty() { "—".to_string() } else { parts.join(" ") }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HWP (Hardware P-states / Intel Speed Shift)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Intel HWP (Hardware P-states) capability and request data read
|
||||
/// from per-CPU MSRs. `None` from `HwpInfo::read()` means HWP is
|
||||
/// not supported (AMD, pre-Skylake Intel, or QEMU without MSR
|
||||
/// passthrough).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HwpInfo {
|
||||
/// Whether HWP has been enabled (IA32_PM_ENABLE bit 0).
|
||||
pub enabled: bool,
|
||||
/// Highest performance level the hardware can deliver
|
||||
/// (IA32_HWP_CAPABILITIES bits 31:24). Arbitrary units — not
|
||||
/// directly MHz, though roughly proportional.
|
||||
pub max_perf: u8,
|
||||
/// Guaranteed performance level (bits 23:16).
|
||||
pub guaranteed_perf: u8,
|
||||
/// Most efficient performance level (bits 15:8).
|
||||
pub efficient_perf: u8,
|
||||
/// Minimum requested performance (IA32_HWP_REQUEST bits 23:16).
|
||||
pub min_request: u8,
|
||||
/// Maximum requested performance (bits 31:24).
|
||||
pub max_request: u8,
|
||||
/// Desired performance target (bits 15:8).
|
||||
pub desired_perf: u8,
|
||||
/// Energy Performance Preference (bits 7:0). Range 0–255:
|
||||
/// 0 = all-out performance, 128 = balanced, 255 = max power
|
||||
/// savings.
|
||||
pub epp: u8,
|
||||
}
|
||||
|
||||
impl HwpInfo {
|
||||
/// Read HWP data for `cpu`. Returns `None` if any of the three
|
||||
/// required MSRs are unreadable.
|
||||
pub fn read(cpu: u32) -> Option<Self> {
|
||||
let enable = read_msr(cpu, IA32_PM_ENABLE)?;
|
||||
let caps = read_msr(cpu, IA32_HWP_CAPABILITIES)?;
|
||||
let req = read_msr(cpu, IA32_HWP_REQUEST)?;
|
||||
Some(Self {
|
||||
enabled: enable & 1 != 0,
|
||||
max_perf: ((caps >> 24) & 0xFF) as u8,
|
||||
guaranteed_perf: ((caps >> 16) & 0xFF) as u8,
|
||||
efficient_perf: ((caps >> 8) & 0xFF) as u8,
|
||||
min_request: ((req >> 16) & 0xFF) as u8,
|
||||
max_request: ((req >> 24) & 0xFF) as u8,
|
||||
desired_perf: ((req >> 8) & 0xFF) as u8,
|
||||
epp: (req & 0xFF) as u8,
|
||||
})
|
||||
}
|
||||
|
||||
/// Human-readable label for the EPP value.
|
||||
pub fn epp_label(&self) -> &'static str {
|
||||
match self.epp {
|
||||
0..=25 => "Performance",
|
||||
26..=100 => "Bal. Perf",
|
||||
101..=155 => "Balanced",
|
||||
156..=220 => "Bal. Power",
|
||||
221..=255 => "Power",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,6 +461,7 @@ pub struct ProcessInfo {
|
||||
/// Process panel renders a compact range-string form
|
||||
/// (e.g. "0-3,5,7-11") for at-a-glance scanning.
|
||||
pub cpu_affinity: Option<Vec<u32>>,
|
||||
pub sched_policy: String,
|
||||
}
|
||||
|
||||
impl ProcessInfo {
|
||||
@@ -834,6 +835,7 @@ fn parse_stat_line(line: &str) -> Option<ProcessInfo> {
|
||||
thread_io_read_rate_kbs: None,
|
||||
thread_io_write_rate_kbs: None,
|
||||
cpu_affinity,
|
||||
sched_policy: String::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -844,9 +846,31 @@ fn read_process(pid: u32) -> Option<ProcessInfo> {
|
||||
if info.comm.is_empty() || info.comm == "?" {
|
||||
info.comm = read_comm(pid);
|
||||
}
|
||||
info.sched_policy = derive_sched_policy(pid, info.priority);
|
||||
Some(info)
|
||||
}
|
||||
|
||||
fn derive_sched_policy(pid: u32, priority: i64) -> String {
|
||||
if let Ok(data) = fs::read(format!("/proc/{}/sched-policy", pid)) {
|
||||
if let Some(&policy) = data.first() {
|
||||
return match policy {
|
||||
0 => "FIFO",
|
||||
1 => "RR",
|
||||
2 => "OTHER",
|
||||
_ => "?",
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
if priority > 0 && priority < 40 {
|
||||
"OTHER".into()
|
||||
} else if priority >= 40 && priority < 100 {
|
||||
"RT".into()
|
||||
} else {
|
||||
"OTHER".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcInfo {
|
||||
pub fn read() -> Self {
|
||||
Self::read_sorted(SortMode::default())
|
||||
|
||||
@@ -386,6 +386,67 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
|
||||
if any_pl { "PL ".set_style(theme::POWER_LIMIT_FLAG) } else { "PL ".set_style(theme::VALUE_OFF) },
|
||||
]));
|
||||
|
||||
{
|
||||
let s = &app.sched_stats;
|
||||
let mut parts: Vec<ratatui::text::Span> = Vec::new();
|
||||
parts.push("Sched: ".set_style(theme::LABEL));
|
||||
let mut wrote = false;
|
||||
if let Some(v) = s.context_switches {
|
||||
parts.push(format!("switches={v} ").set_style(theme::VALUE));
|
||||
wrote = true;
|
||||
}
|
||||
if let Some(v) = s.contexts_created {
|
||||
parts.push(format!("created={v} ").set_style(theme::VALUE));
|
||||
wrote = true;
|
||||
}
|
||||
if let Some(v) = s.contexts_running {
|
||||
parts.push(format!("running={v} ").set_style(theme::VALUE));
|
||||
wrote = true;
|
||||
}
|
||||
if let Some(v) = s.contexts_blocked {
|
||||
parts.push(format!("blocked={v} ").set_style(theme::VALUE));
|
||||
wrote = true;
|
||||
}
|
||||
if let Some(v) = s.total_irqs {
|
||||
parts.push(format!("IRQs={v} ").set_style(theme::VALUE));
|
||||
wrote = true;
|
||||
}
|
||||
if !s.per_cpu_steals.is_empty() {
|
||||
let total_steals: u64 = s.per_cpu_steals.iter().sum();
|
||||
parts.push(format!("steals={total_steals} ").set_style(theme::VALUE));
|
||||
wrote = true;
|
||||
}
|
||||
if !s.per_cpu_queue_depth.is_empty() {
|
||||
let total_qd: u64 = s.per_cpu_queue_depth.iter().sum();
|
||||
let avg_qd = total_qd / s.per_cpu_queue_depth.len() as u64;
|
||||
parts.push(format!("avg_qd={avg_qd}").set_style(theme::VALUE));
|
||||
wrote = true;
|
||||
}
|
||||
if wrote {
|
||||
lines.push(Line::from(parts));
|
||||
}
|
||||
}
|
||||
|
||||
if app.pkg_power_w.is_some() || app.pp0_power_w.is_some()
|
||||
|| app.pp1_power_w.is_some() || app.dram_power_w.is_some()
|
||||
{
|
||||
let mut parts: Vec<ratatui::text::Span> = Vec::new();
|
||||
parts.push("Power: ".set_style(theme::LABEL));
|
||||
if let Some(w) = app.pkg_power_w {
|
||||
parts.push(format!("Pkg {w:.1}W ").set_style(theme::VALUE));
|
||||
}
|
||||
if let Some(w) = app.pp0_power_w {
|
||||
parts.push(format!("Core {w:.1}W ").set_style(theme::VALUE));
|
||||
}
|
||||
if let Some(w) = app.pp1_power_w {
|
||||
parts.push(format!("Uncore {w:.1}W ").set_style(theme::VALUE));
|
||||
}
|
||||
if let Some(w) = app.dram_power_w {
|
||||
parts.push(format!("DRAM {w:.1}W").set_style(theme::VALUE));
|
||||
}
|
||||
lines.push(Line::from(parts));
|
||||
}
|
||||
|
||||
// OS identity (matches cpu-x System tab)
|
||||
if app.os_info.available {
|
||||
lines.push(Line::from(vec![
|
||||
@@ -435,8 +496,26 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let cs = &app.collector_stats;
|
||||
if cs.thread_count > 1 {
|
||||
lines.push(Line::from(vec![
|
||||
"Collector: ".set_style(theme::LABEL),
|
||||
format!("{} threads", cs.thread_count).set_style(theme::VALUE),
|
||||
" pinned: ".set_style(theme::LABEL),
|
||||
format!("{}/{}", cs.pinned_count, cs.barrier_size).set_style(theme::VALUE),
|
||||
" barrier: ".set_style(theme::LABEL),
|
||||
format!("{}", cs.barrier_size).set_style(theme::VALUE),
|
||||
" last: ".set_style(theme::LABEL),
|
||||
format!("{:.2} ms", cs.elapsed_us as f64 / 1000.0).set_style(theme::VALUE),
|
||||
]));
|
||||
} else {
|
||||
lines.push(Line::from(vec![
|
||||
"Collector: ".set_style(theme::LABEL),
|
||||
"single-threaded".set_style(theme::VALUE_OFF),
|
||||
]));
|
||||
}
|
||||
|
||||
lines.push(Line::from(vec![
|
||||
"Benchmark: ".set_style(theme::LABEL),
|
||||
if app.bench_line.is_empty() { "(idle)".set_style(theme::VALUE_OFF) } else { app.bench_line.as_str().set_style(theme::VALUE) },
|
||||
]));
|
||||
Paragraph::new(lines)
|
||||
@@ -946,7 +1025,7 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
|
||||
_ => "RSS",
|
||||
};
|
||||
let header_str = format!(
|
||||
" PID STATE PRIO NI THR CPU% IO RATE {:<11} T-IO T-IO/s IO-RATE CPU% RSS AFF COMM",
|
||||
" PID STATE SCHED PRIO NI THR CPU% IO RATE {:<11} T-IO T-IO/s IO-RATE CPU% RSS AFF COMM",
|
||||
mem_header
|
||||
);
|
||||
lines.push(Line::from(vec![
|
||||
@@ -1058,10 +1137,11 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
|
||||
theme::VALUE
|
||||
};
|
||||
lines.push(Line::from(format!(
|
||||
" {}{:<7} {} {:<4} {:<3} {:<3} {:<6} {:<11} {:<11} {:<11} {:<11} {:<11} {:<12} {:<6} {:<5} {:<3} {}",
|
||||
" {}{:<7} {} {:<5} {:<4} {:<3} {:<3} {:<6} {:<11} {:<11} {:<11} {:<11} {:<11} {:<12} {:<6} {:<5} {:<3} {}",
|
||||
prefix,
|
||||
p.pid,
|
||||
p.state,
|
||||
p.sched_policy,
|
||||
p.priority,
|
||||
p.nice,
|
||||
p.num_threads,
|
||||
@@ -1405,6 +1485,7 @@ pub fn render_cpu_table<'a>(
|
||||
focused: bool,
|
||||
pkg_power_w: Option<f64>,
|
||||
rapl_status: &'a str,
|
||||
per_cpu_irqs: &'a [u64],
|
||||
) -> Table<'a> {
|
||||
let header = Row::new(vec![
|
||||
"CPU".set_style(theme::LABEL),
|
||||
@@ -1415,6 +1496,7 @@ pub fn render_cpu_table<'a>(
|
||||
"State".set_style(theme::LABEL),
|
||||
"Flags".set_style(theme::LABEL),
|
||||
"Load % (30s)".set_style(theme::LABEL),
|
||||
"IRQs".set_style(theme::LABEL),
|
||||
])
|
||||
.height(1);
|
||||
let rows: Vec<Row> = cpus
|
||||
@@ -1483,6 +1565,15 @@ pub fn render_cpu_table<'a>(
|
||||
spark_text.set_style(Style::new().fg(lcolor)),
|
||||
format!(" {load_label}").set_style(Style::new().fg(lcolor).bold()),
|
||||
])),
|
||||
{
|
||||
let irq_val = per_cpu_irqs.get(cpu.id as usize).copied();
|
||||
Cell::from(
|
||||
irq_val
|
||||
.map(|v| format!("{v}"))
|
||||
.unwrap_or_else(|| "—".into())
|
||||
.set_style(if irq_val.is_some() { theme::VALUE } else { theme::VALUE_OFF }),
|
||||
)
|
||||
},
|
||||
])
|
||||
})
|
||||
.collect();
|
||||
@@ -1511,6 +1602,32 @@ pub fn render_cpu_table<'a>(
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
]));
|
||||
}
|
||||
if let Some(ref hwp) = cpu.hwp {
|
||||
let s = sub_style;
|
||||
rows.push(Row::new(vec![
|
||||
" HWP caps".set_style(s),
|
||||
format!("eff={} guar={} max={}", hwp.efficient_perf, hwp.guaranteed_perf, hwp.max_perf).set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
format!("{}", if hwp.enabled { "ON" } else { "OFF" }).set_style(if hwp.enabled { Style::new().green() } else { theme::VALUE_OFF }),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
]));
|
||||
rows.push(Row::new(vec![
|
||||
" HWP req".set_style(s),
|
||||
format!("min={} max={} des={} EPP={} ({})", hwp.min_request, hwp.max_request, hwp.desired_perf, hwp.epp, hwp.epp_label()).set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
"".set_style(s),
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -1526,6 +1643,7 @@ pub fn render_cpu_table<'a>(
|
||||
Constraint::Length(8),
|
||||
Constraint::Length(6),
|
||||
Constraint::Length(SPARK_WIDTH as u16 + 6),
|
||||
Constraint::Length(8),
|
||||
],
|
||||
)
|
||||
.header(header)
|
||||
@@ -1671,7 +1789,7 @@ pub fn snapshot(app: &App, width: u16, height: u16) -> String {
|
||||
);
|
||||
f.render_widget(render_header(app, true), header_area);
|
||||
f.render_stateful_widget(
|
||||
render_cpu_table(&app.cpus, app.expanded_cpu, true, app.pkg_power_w, &app.rapl_status),
|
||||
render_cpu_table(&app.cpus, app.expanded_cpu, true, app.pkg_power_w, &app.rapl_status, &app.sched_stats.per_cpu_irqs),
|
||||
table_area,
|
||||
&mut state,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
//! Scheduler statistics from `/scheme/sys/stat` (Redox) or
|
||||
//! `/proc/stat` (Linux).
|
||||
//!
|
||||
//! Redox's sys scheme exposes a richer set of scheduler counters than
|
||||
//! Linux: per-CPU IRQ counts, context-switch totals, and scheduler
|
||||
//! context state (created / running / blocked). On Linux, only the
|
||||
//! `ctxt` (context switches) and `intr` (total interrupts) lines are
|
||||
//! available from `/proc/stat`; per-CPU IRQ distribution lives in
|
||||
//! `/proc/interrupts` and is not read here (kept cheap — one file
|
||||
//! read).
|
||||
//!
|
||||
//! All fields degrade to `None` / empty when the data source is absent
|
||||
//! or unparseable, so the TUI never panics on a missing scheme.
|
||||
|
||||
use std::fs;
|
||||
|
||||
/// Scheduler and IRQ statistics. Fields are `Option` because the
|
||||
/// underlying data source may not provide all of them (Linux vs
|
||||
/// Redox). `Default` gives all-`None` / empty, suitable as a
|
||||
/// pre-probe placeholder.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SchedStats {
|
||||
pub context_switches: Option<u64>,
|
||||
pub contexts_created: Option<u64>,
|
||||
pub contexts_running: Option<u64>,
|
||||
pub contexts_blocked: Option<u64>,
|
||||
pub per_cpu_irqs: Vec<u64>,
|
||||
pub total_irqs: Option<u64>,
|
||||
pub per_cpu_switches: Vec<u64>,
|
||||
pub per_cpu_steals: Vec<u64>,
|
||||
pub per_cpu_queue_depth: Vec<u64>,
|
||||
}
|
||||
|
||||
impl SchedStats {
|
||||
/// Read scheduler stats from the first available data source.
|
||||
///
|
||||
/// Tries `/scheme/sys/stat` (Redox) first, then falls back to
|
||||
/// `/proc/stat` (Linux). Returns a `SchedStats` with `None` /
|
||||
/// empty fields for anything the source does not provide.
|
||||
pub fn read() -> Self {
|
||||
let mut stats = Self::default();
|
||||
|
||||
if let Ok(data) = fs::read_to_string("/scheme/sys/stat") {
|
||||
stats = Self::parse_redox(&data);
|
||||
} else if let Ok(data) = fs::read_to_string("/proc/stat") {
|
||||
stats = Self::parse_linux(&data);
|
||||
}
|
||||
|
||||
if let Ok(data) = fs::read_to_string("/scheme/sys/sched") {
|
||||
Self::merge_sched_detail(&mut stats, &data);
|
||||
}
|
||||
|
||||
stats
|
||||
}
|
||||
|
||||
/// Parse the Redox `/scheme/sys/stat` format:
|
||||
/// ```text
|
||||
/// cpu <u> <n> <k> <i> <irq>
|
||||
/// cpu0 ...
|
||||
/// IRQs <total> <cpu0_irqs> <cpu1_irqs> ...
|
||||
/// boot_time: <secs>
|
||||
/// context_switches: <num>
|
||||
/// contexts_created: <num>
|
||||
/// contexts_running: <num>
|
||||
/// contexts_blocked: <num>
|
||||
/// ```
|
||||
fn parse_redox(data: &str) -> Self {
|
||||
let mut stats = Self::default();
|
||||
for line in data.lines() {
|
||||
let trimmed = line.trim();
|
||||
if let Some(rest) = trimmed.strip_prefix("IRQs ") {
|
||||
// "IRQs <total> <cpu0> <cpu1> ..."
|
||||
let nums: Vec<u64> = rest
|
||||
.split_whitespace()
|
||||
.filter_map(|s| s.parse().ok())
|
||||
.collect();
|
||||
// First number is the total; remaining are per-CPU.
|
||||
if let Some(&total) = nums.first() {
|
||||
stats.total_irqs = Some(total);
|
||||
stats.per_cpu_irqs = nums[1..].to_vec();
|
||||
}
|
||||
} else if let Some(rest) = trimmed.strip_prefix("context_switches:") {
|
||||
stats.context_switches = rest.trim().parse().ok();
|
||||
} else if let Some(rest) = trimmed.strip_prefix("contexts_created:") {
|
||||
stats.contexts_created = rest.trim().parse().ok();
|
||||
} else if let Some(rest) = trimmed.strip_prefix("contexts_running:") {
|
||||
stats.contexts_running = rest.trim().parse().ok();
|
||||
} else if let Some(rest) = trimmed.strip_prefix("contexts_blocked:") {
|
||||
stats.contexts_blocked = rest.trim().parse().ok();
|
||||
}
|
||||
}
|
||||
stats
|
||||
}
|
||||
|
||||
/// Parse the Linux `/proc/stat` format. Only `ctxt` (context
|
||||
/// switches) and `intr` (total interrupts) are available.
|
||||
/// Per-CPU IRQ distribution is in `/proc/interrupts` and is not
|
||||
/// read here.
|
||||
///
|
||||
/// ```text
|
||||
/// cpu 1000 200 3000 50000 ...
|
||||
/// cpu0 ...
|
||||
/// intr 200 50 50 ... (first number = total)
|
||||
/// ctxt 5000
|
||||
/// btime 1000
|
||||
/// ```
|
||||
fn parse_linux(data: &str) -> Self {
|
||||
let mut stats = Self::default();
|
||||
for line in data.lines() {
|
||||
let trimmed = line.trim();
|
||||
// "ctxt <number>" — context switches since boot.
|
||||
if let Some(rest) = trimmed.strip_prefix("ctxt ") {
|
||||
stats.context_switches = rest.trim().parse().ok();
|
||||
}
|
||||
// "intr <total> <irq0> <irq1> ..." — first field is total.
|
||||
if let Some(rest) = trimmed.strip_prefix("intr ") {
|
||||
if let Some(total_str) = rest.split_whitespace().next() {
|
||||
stats.total_irqs = total_str.parse().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
stats
|
||||
}
|
||||
|
||||
fn merge_sched_detail(stats: &mut SchedStats, data: &str) {
|
||||
for line in data.lines() {
|
||||
let trimmed = line.trim();
|
||||
if let Some(rest) = trimmed.strip_prefix("cpu") {
|
||||
let parts: Vec<&str> = rest.split_whitespace().collect();
|
||||
if parts.len() < 7 {
|
||||
continue;
|
||||
}
|
||||
let switches: u64 = parts[2].parse().unwrap_or(0);
|
||||
let steals: u64 = parts[4].parse().unwrap_or(0);
|
||||
let qd: u64 = parts[6].parse().unwrap_or(0);
|
||||
stats.per_cpu_switches.push(switches);
|
||||
stats.per_cpu_steals.push(steals);
|
||||
stats.per_cpu_queue_depth.push(qd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_redox_all_fields() {
|
||||
let data = "\
|
||||
cpu 1000 200 3000 50000 100
|
||||
cpu0 500 100 1500 25000 50
|
||||
cpu1 500 100 1500 25000 50
|
||||
IRQs 200 100 100
|
||||
boot_time: 1000
|
||||
context_switches: 5000
|
||||
contexts_created: 100
|
||||
contexts_running: 2
|
||||
contexts_blocked: 0
|
||||
";
|
||||
let stats = SchedStats::parse_redox(data);
|
||||
assert_eq!(stats.context_switches, Some(5000));
|
||||
assert_eq!(stats.contexts_created, Some(100));
|
||||
assert_eq!(stats.contexts_running, Some(2));
|
||||
assert_eq!(stats.contexts_blocked, Some(0));
|
||||
assert_eq!(stats.total_irqs, Some(200));
|
||||
assert_eq!(stats.per_cpu_irqs, vec![100, 100]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_linux_context_switches_and_intr() {
|
||||
let data = "\
|
||||
cpu 1000 200 3000 50000 100 0 0 0 0 0
|
||||
cpu0 500 100 1500 25000 50 0 0 0 0 0
|
||||
intr 200 50 50 100
|
||||
ctxt 5000
|
||||
btime 1000
|
||||
";
|
||||
let stats = SchedStats::parse_linux(data);
|
||||
assert_eq!(stats.context_switches, Some(5000));
|
||||
assert_eq!(stats.total_irqs, Some(200));
|
||||
// Per-CPU IRQs are NOT available from /proc/stat on Linux.
|
||||
assert!(stats.per_cpu_irqs.is_empty());
|
||||
// Redox-specific fields are not available.
|
||||
assert_eq!(stats.contexts_created, None);
|
||||
assert_eq!(stats.contexts_running, None);
|
||||
assert_eq!(stats.contexts_blocked, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_is_all_none() {
|
||||
let stats = SchedStats::default();
|
||||
assert_eq!(stats.context_switches, None);
|
||||
assert_eq!(stats.total_irqs, None);
|
||||
assert!(stats.per_cpu_irqs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_redox_missing_context_fields() {
|
||||
// Only the IRQs line — no context_* lines.
|
||||
let data = "IRQs 42 10 20 12\n";
|
||||
let stats = SchedStats::parse_redox(data);
|
||||
assert_eq!(stats.total_irqs, Some(42));
|
||||
assert_eq!(stats.per_cpu_irqs, vec![10, 20, 12]);
|
||||
assert_eq!(stats.context_switches, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_redox_empty_irqs_line() {
|
||||
let data = "cpu 100 0 0 100 0\ncontext_switches: 99\n";
|
||||
let stats = SchedStats::parse_redox(data);
|
||||
assert_eq!(stats.context_switches, Some(99));
|
||||
assert_eq!(stats.total_irqs, None);
|
||||
assert!(stats.per_cpu_irqs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_sched_detail_parses_per_cpu() {
|
||||
let mut stats = SchedStats::default();
|
||||
let data = "\
|
||||
cpu0 switches 1000 steals 5 queue_depth 3
|
||||
cpu1 switches 2000 steals 2 queue_depth 1
|
||||
total switches 3000 steals 7
|
||||
";
|
||||
SchedStats::merge_sched_detail(&mut stats, data);
|
||||
assert_eq!(stats.per_cpu_switches, vec![1000, 2000]);
|
||||
assert_eq!(stats.per_cpu_steals, vec![5, 2]);
|
||||
assert_eq!(stats.per_cpu_queue_depth, vec![3, 1]);
|
||||
}
|
||||
}
|
||||
+1
-1
Submodule local/sources/kernel updated: e812356cf0...c6a5b7a1ad
+1
-1
Submodule local/sources/relibc updated: 9774052fd1...26595f1624
Reference in New Issue
Block a user