From bbc27c4085f5373a0d5fcd13187a5a9bcd0f4251 Mon Sep 17 00:00:00 2001 From: Bjoern Walk Date: Thu, 3 Jul 2025 09:48:24 +0200 Subject: [PATCH 1/2] bindings: add Rust bindings Rust bindings implement an FFI interface to the qclib C library and allows the usage of qclib in Rust context. The bindings come in two crates: * qclib-sys: contains the raw FFI interface to the C library. * qclib: contains a safe and idiomatic abstraction for data types and functions provided by the C library. Signed-off-by: Bjoern Walk --- bindings/rust/.gitignore | 1 + bindings/rust/Cargo.lock | 100 ++++ bindings/rust/Cargo.toml | 22 + bindings/rust/LICENSE | 1 + bindings/rust/qclib-sys/Cargo.toml | 7 + bindings/rust/qclib-sys/build.rs | 3 + bindings/rust/qclib-sys/src/lib.rs | 132 +++++ bindings/rust/src/enums.rs | 498 ++++++++++++++++++ bindings/rust/src/error.rs | 12 + bindings/rust/src/layer.rs | 123 +++++ bindings/rust/src/layers/cec.rs | 82 +++ bindings/rust/src/layers/kvm_guest.rs | 48 ++ bindings/rust/src/layers/kvm_hypervisor.rs | 42 ++ bindings/rust/src/layers/lpar.rs | 84 +++ bindings/rust/src/layers/lpar_group.rs | 1 + bindings/rust/src/layers/mod.rs | 59 +++ bindings/rust/src/layers/zos_hypervisor.rs | 1 + .../src/layers/zos_tenant_resource_group.rs | 1 + bindings/rust/src/layers/zos_zcx_server.rs | 1 + bindings/rust/src/layers/zvm_cpu_pool.rs | 1 + bindings/rust/src/layers/zvm_guest.rs | 80 +++ bindings/rust/src/layers/zvm_hypervisor.rs | 60 +++ bindings/rust/src/layers/zvm_resource_pool.rs | 1 + bindings/rust/src/lib.rs | 193 +++++++ bindings/rust/src/macros.rs | 75 +++ 25 files changed, 1628 insertions(+) create mode 100644 bindings/rust/.gitignore create mode 100644 bindings/rust/Cargo.lock create mode 100644 bindings/rust/Cargo.toml create mode 120000 bindings/rust/LICENSE create mode 100644 bindings/rust/qclib-sys/Cargo.toml create mode 100644 bindings/rust/qclib-sys/build.rs create mode 100644 bindings/rust/qclib-sys/src/lib.rs create mode 100644 bindings/rust/src/enums.rs create mode 100644 bindings/rust/src/error.rs create mode 100644 bindings/rust/src/layer.rs create mode 100644 bindings/rust/src/layers/cec.rs create mode 100644 bindings/rust/src/layers/kvm_guest.rs create mode 100644 bindings/rust/src/layers/kvm_hypervisor.rs create mode 100644 bindings/rust/src/layers/lpar.rs create mode 100644 bindings/rust/src/layers/lpar_group.rs create mode 100644 bindings/rust/src/layers/mod.rs create mode 100644 bindings/rust/src/layers/zos_hypervisor.rs create mode 100644 bindings/rust/src/layers/zos_tenant_resource_group.rs create mode 100644 bindings/rust/src/layers/zos_zcx_server.rs create mode 100644 bindings/rust/src/layers/zvm_cpu_pool.rs create mode 100644 bindings/rust/src/layers/zvm_guest.rs create mode 100644 bindings/rust/src/layers/zvm_hypervisor.rs create mode 100644 bindings/rust/src/layers/zvm_resource_pool.rs create mode 100644 bindings/rust/src/lib.rs create mode 100644 bindings/rust/src/macros.rs diff --git a/bindings/rust/.gitignore b/bindings/rust/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/bindings/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/bindings/rust/Cargo.lock b/bindings/rust/Cargo.lock new file mode 100644 index 00000000..ff9cc411 --- /dev/null +++ b/bindings/rust/Cargo.lock @@ -0,0 +1,100 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qclib" +version = "2.0.0" +dependencies = [ + "qclib-sys", + "serde_json", +] + +[[package]] +name = "qclib-sys" +version = "2.0.0" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[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 = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +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" diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 00000000..a2575437 --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,22 @@ +[workspace.package] +edition = "2021" +license-file = "LICENSE" + +[package] +name = "qclib" +version = "2.0.0" +edition.workspace = true +license-file.workspace = true +description = "Rust bindings for qclib (Query Capacity Library)" + +[dependencies] +serde_json = { version = "1.0", optional = true } + +[features] +all = ["json"] +json = ["dep:serde_json"] + +[dependencies.sys] +package = "qclib-sys" +path = "qclib-sys" +version = "2" diff --git a/bindings/rust/LICENSE b/bindings/rust/LICENSE new file mode 120000 index 00000000..30cff740 --- /dev/null +++ b/bindings/rust/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/bindings/rust/qclib-sys/Cargo.toml b/bindings/rust/qclib-sys/Cargo.toml new file mode 100644 index 00000000..070d2af5 --- /dev/null +++ b/bindings/rust/qclib-sys/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "qclib-sys" +version = "2.0.0" +edition.workspace = true +license-file.workspace = true +description = "Raw bindings for qclib (Query Capacity Library)" +links = "qc" diff --git a/bindings/rust/qclib-sys/build.rs b/bindings/rust/qclib-sys/build.rs new file mode 100644 index 00000000..99d9248d --- /dev/null +++ b/bindings/rust/qclib-sys/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo::rustc-link-lib=dylib:+verbatim=libqc.so.2"); +} diff --git a/bindings/rust/qclib-sys/src/lib.rs b/bindings/rust/qclib-sys/src/lib.rs new file mode 100644 index 00000000..1b4b7546 --- /dev/null +++ b/bindings/rust/qclib-sys/src/lib.rs @@ -0,0 +1,132 @@ +#![allow(nonstandard_style)] + +pub type qc_layer_type = ::std::os::raw::c_uint; +pub const QC_LAYER_TYPE_CEC: qc_layer_type = 1; +pub const QC_LAYER_TYPE_LPAR_GROUP: qc_layer_type = 8; +pub const QC_LAYER_TYPE_LPAR: qc_layer_type = 2; +pub const QC_LAYER_TYPE_ZVM_HYPERVISOR: qc_layer_type = 3; +pub const QC_LAYER_TYPE_ZVM_CPU_POOL: qc_layer_type = 4; +pub const QC_LAYER_TYPE_ZVM_RESOURCE_POOL: qc_layer_type = 4; +pub const QC_LAYER_TYPE_ZVM_GUEST: qc_layer_type = 5; +pub const QC_LAYER_TYPE_KVM_HYPERVISOR: qc_layer_type = 6; +pub const QC_LAYER_TYPE_KVM_GUEST: qc_layer_type = 7; +pub const QC_LAYER_TYPE_ZOS_HYPERVISOR: qc_layer_type = 9; +pub const QC_LAYER_TYPE_ZOS_TENANT_RESOURCE_GROUP: qc_layer_type = 10; +pub const QC_LAYER_TYPE_ZOS_ZCX_SERVER: qc_layer_type = 11; + +pub type qc_layer_category = ::std::os::raw::c_uint; +pub const QC_LAYER_CAT_GUEST: qc_layer_category = 1; +pub const QC_LAYER_CAT_HOST: qc_layer_category = 2; +pub const QC_LAYER_CAT_POOL: qc_layer_category = 3; + +pub type qc_attr_id = ::std::os::raw::c_uint; +pub const QC_ADJUSTMENT: qc_attr_id = 0; +pub const QC_CAPABILITY: qc_attr_id = 1; +pub const QC_CAPACITY_ADJUSTMENT_INDICATION: qc_attr_id = 2; +pub const QC_CAPACITY_CHANGE_REASON: qc_attr_id = 3; +pub const QC_CAPPING: qc_attr_id = 4; +pub const QC_CAPPING_NUM: qc_attr_id = 5; +pub const QC_CLUSTER_NAME: qc_attr_id = 6; +pub const QC_CONTROL_PROGRAM_ID: qc_attr_id = 7; +pub const QC_CP_ABSOLUTE_CAPPING: qc_attr_id = 8; +pub const QC_CP_CAPACITY_CAP: qc_attr_id = 9; +pub const QC_CP_CAPPED_CAPACITY: qc_attr_id = 10; +pub const QC_CP_DISPATCH_LIMITHARD: qc_attr_id = 11; +pub const QC_CP_DISPATCH_TYPE: qc_attr_id = 12; +pub const QC_CP_LIMITHARD_CAP: qc_attr_id = 13; +pub const QC_CP_WEIGHT_CAPPING: qc_attr_id = 14; +pub const QC_LIMITHARD_CONSUMPTION: qc_attr_id = 15; +pub const QC_HARDLIMIT_CONSUMPTION: qc_attr_id = 15; +pub const QC_HAS_MULTIPLE_CPU_TYPES: qc_attr_id = 16; +pub const QC_IFL_ABSOLUTE_CAPPING: qc_attr_id = 17; +pub const QC_IFL_CAPACITY_CAP: qc_attr_id = 18; +pub const QC_IFL_CAPPED_CAPACITY: qc_attr_id = 19; +pub const QC_IFL_DISPATCH_LIMITHARD: qc_attr_id = 20; +pub const QC_IFL_DISPATCH_TYPE: qc_attr_id = 21; +pub const QC_IFL_LIMITHARD_CAP: qc_attr_id = 22; +pub const QC_IFL_WEIGHT_CAPPING: qc_attr_id = 23; +pub const QC_ZIIP_ABSOLUTE_CAPPING: qc_attr_id = 66; +pub const QC_ZIIP_CAPACITY_CAP: qc_attr_id = 67; +pub const QC_ZIIP_CAPPED_CAPACITY: qc_attr_id = 68; +pub const QC_ZIIP_DISPATCH_LIMITHARD: qc_attr_id = 69; +pub const QC_ZIIP_DISPATCH_TYPE: qc_attr_id = 70; +pub const QC_ZIIP_LIMITHARD_CAP: qc_attr_id = 71; +pub const QC_ZIIP_WEIGHT_CAPPING: qc_attr_id = 72; +pub const QC_LAYER_CATEGORY: qc_attr_id = 24; +pub const QC_LAYER_CATEGORY_NUM: qc_attr_id = 25; +pub const QC_LAYER_EXTENDED_NAME: qc_attr_id = 26; +pub const QC_LAYER_NAME: qc_attr_id = 27; +pub const QC_LAYER_TYPE: qc_attr_id = 28; +pub const QC_LAYER_TYPE_NUM: qc_attr_id = 29; +pub const QC_LAYER_UUID: qc_attr_id = 30; +pub const QC_MANUFACTURER: qc_attr_id = 31; +pub const QC_MOBILITY_ENABLED: qc_attr_id = 32; +pub const QC_MOBILITY_ELIGIBLE: qc_attr_id = 32; +pub const QC_HAS_SECURE: qc_attr_id = 77; +pub const QC_SECURE: qc_attr_id = 78; +pub const QC_MODEL: qc_attr_id = 33; +pub const QC_MODEL_CAPACITY: qc_attr_id = 34; +pub const QC_TYPE_FAMILY: qc_attr_id = 65; +pub const QC_NUM_CP_DEDICATED: qc_attr_id = 35; +pub const QC_NUM_CP_SHARED: qc_attr_id = 36; +pub const QC_NUM_CP_TOTAL: qc_attr_id = 37; +pub const QC_NUM_CPU_CONFIGURED: qc_attr_id = 38; +pub const QC_NUM_CPU_DEDICATED: qc_attr_id = 39; +pub const QC_NUM_CPU_RESERVED: qc_attr_id = 40; +pub const QC_NUM_CPU_SHARED: qc_attr_id = 41; +pub const QC_NUM_CPU_STANDBY: qc_attr_id = 42; +pub const QC_NUM_CPU_TOTAL: qc_attr_id = 43; +pub const QC_NUM_IFL_DEDICATED: qc_attr_id = 44; +pub const QC_NUM_IFL_SHARED: qc_attr_id = 45; +pub const QC_NUM_IFL_TOTAL: qc_attr_id = 46; +pub const QC_NUM_ZIIP_DEDICATED: qc_attr_id = 73; +pub const QC_NUM_ZIIP_SHARED: qc_attr_id = 74; +pub const QC_NUM_ZIIP_TOTAL: qc_attr_id = 75; +pub const QC_PARTITION_CHAR: qc_attr_id = 47; +pub const QC_PARTITION_CHAR_NUM: qc_attr_id = 48; +pub const QC_PARTITION_NUMBER: qc_attr_id = 49; +pub const QC_PLANT: qc_attr_id = 50; +pub const QC_SECONDARY_CAPABILITY: qc_attr_id = 51; +pub const QC_SEQUENCE_CODE: qc_attr_id = 52; +pub const QC_TYPE: qc_attr_id = 53; +pub const QC_PRORATED_CORE_TIME: qc_attr_id = 54; +pub const QC_NUM_CP_THREADS: qc_attr_id = 55; +pub const QC_NUM_IFL_THREADS: qc_attr_id = 56; +pub const QC_NUM_ZIIP_THREADS: qc_attr_id = 76; +pub const QC_NUM_CORE_TOTAL: qc_attr_id = 57; +pub const QC_NUM_CORE_CONFIGURED: qc_attr_id = 58; +pub const QC_NUM_CORE_STANDBY: qc_attr_id = 59; +pub const QC_NUM_CORE_RESERVED: qc_attr_id = 60; +pub const QC_NUM_CORE_DEDICATED: qc_attr_id = 61; +pub const QC_NUM_CORE_SHARED: qc_attr_id = 62; +pub const QC_TYPE_NAME: qc_attr_id = 63; +pub const QC_LIC_IDENTIFIER: qc_attr_id = 64; + +extern "C" { + pub fn qc_open(rc: *mut ::std::os::raw::c_int) -> *mut ::std::os::raw::c_void; + pub fn qc_close(hdl: *mut ::std::os::raw::c_void); + + pub fn qc_get_num_layers( + hdl: *mut ::std::os::raw::c_void, + rc: *mut ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; + + pub fn qc_get_attribute_string( + hdl: *mut ::std::os::raw::c_void, + id: qc_attr_id, + layer: ::std::os::raw::c_int, + value: *mut *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; + pub fn qc_get_attribute_int( + hdl: *mut ::std::os::raw::c_void, + id: qc_attr_id, + layer: ::std::os::raw::c_int, + value: *mut ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; + pub fn qc_get_attribute_float( + hdl: *mut ::std::os::raw::c_void, + id: qc_attr_id, + layer: ::std::os::raw::c_int, + value: *mut ::std::os::raw::c_float, + ) -> ::std::os::raw::c_int; +} diff --git a/bindings/rust/src/enums.rs b/bindings/rust/src/enums.rs new file mode 100644 index 00000000..051008f5 --- /dev/null +++ b/bindings/rust/src/enums.rs @@ -0,0 +1,498 @@ +#![allow(clippy::upper_case_acronyms)] +use sys::*; + +macro_rules! enum_def { + ( + $(#[$attr:meta])* + $vis:vis enum $name:ident: $T:ty { + $( + $(#[doc = $doc:literal])* + #[display($display:literal)] + $item:ident = $value:expr + ),+ $(,)? + } + ) => { + $(#[$attr])* + $vis enum $name { + $( + $(#[doc = $doc])* + $item, + )+ + } + + impl $name { + #[allow(dead_code)] + #[inline] + pub(crate) fn from_raw(raw: $T) -> Self { + $(if raw == $value { + Self::$item + } else )+ { + panic!("invalid variant"); + } + } + + #[allow(dead_code)] + #[inline] + pub(crate) fn as_raw(&self) -> $T { + match *self { + $(Self::$item => $value,)+ + } + } + } + + impl ::std::fmt::Display for $name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let s = match *self { + $(Self::$item => $display,)+ + }; + s.fmt(f) + } + } + }; +} + +enum_def! { + /// Layer types. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum LayerType: qc_layer_type { + /// CEC + #[display("CEC")] + CEC = QC_LAYER_TYPE_CEC, + + /// LPAR Capping Group + #[display("LPAR-GROUP")] + LparGroup = QC_LAYER_TYPE_LPAR_GROUP, + + /// LPAR + #[display("LPAR")] + Lpar = QC_LAYER_TYPE_LPAR, + + /// z/VM Hypervisor + #[display("z/VM-hypervisor")] + ZvmHypervisor = QC_LAYER_TYPE_ZVM_HYPERVISOR, + + /// z/VM CPU Pool (deprecated, use QC_LAYER_TYPE_ZVM_RESOURCE_POOL instead) + #[display("z/VM-CPU-pool")] + ZvmCpuPool = QC_LAYER_TYPE_ZVM_CPU_POOL, + + /// z/VM Resource Pool + #[display("z/VM-resource-pool")] + ZvmResourcePool = QC_LAYER_TYPE_ZVM_RESOURCE_POOL, + + /// z/VM Guest + #[display("z/VM-guest")] + ZvmGuest = QC_LAYER_TYPE_ZVM_GUEST, + + /// KVM Hypervisor + #[display("KVM-hypervisor")] + KvmHypervisor = QC_LAYER_TYPE_KVM_HYPERVISOR, + + /// KVM Guest + #[display("KVM-guest")] + KvmGuest = QC_LAYER_TYPE_KVM_GUEST, + + /// z/OS Hypervisor + #[display("z/OS-hypervisor")] + ZosHypervisor = QC_LAYER_TYPE_ZOS_HYPERVISOR, + + /// z/OS Tenant Resource Group + #[display("z/OS-tenant-resource-group")] + ZosTenantResourceGroup = QC_LAYER_TYPE_ZOS_TENANT_RESOURCE_GROUP, + + /// z/OS cCX Server + #[display("z/OS-zCX-Server")] + ZosZcxServer = QC_LAYER_TYPE_ZOS_ZCX_SERVER, + } +} + +enum_def! { + /// Layer categories. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum LayerCategory: qc_layer_category { + /// Layer category for guests, namely LPARs, z/VM and KVM guests. + #[display("GUEST")] + Guest = QC_LAYER_CAT_GUEST, + + /// Layer category for hosts, namely CEC, z/VM and KVM hosts. + #[display("HOST")] + Host = QC_LAYER_CAT_HOST, + + /// Layer category for pools (currently z/VM Pools and LPAR capping groups). + #[display("POOL")] + Pool = QC_LAYER_CAT_POOL, + } +} + +enum_def! { + /// Layer attributes. + /// + /// Attributes can exist for multiple layers and will only be valid if retrieved from the + /// correct layer type. Retrieving attribute values should be performed via the + /// attribute-related methods on the corresponding layer-specific handle (see + /// [`LayerDetails`]). + /// + /// [`LayerDetails`]: crate::LayerDetails + #[derive(Clone, Copy, Debug)] + pub enum AttributeId: qc_attr_id { + /// The adjustment factor indicates the maximum percentage of the machine (in parts of + /// 1000) that could be used by the primary processor type in the worst case by the + /// respective layer; taking cappings and other limiting factors into consideration. + /// Note: This value can lead to wrong conclusions for layers that utilize more than one + /// processor type! + #[display("adjustment")] + Adjustment = QC_ADJUSTMENT, + + /// Capability rating, see STSI instruction in [2]. + #[display("capability")] + Capability = QC_CAPABILITY, + + /// Capacity adjustment value, see STSI instruction in [2]. + #[display("capacity_adjustment_indication")] + CapacityAdjustmentIndication = QC_CAPACITY_ADJUSTMENT_INDICATION, + + /// Reason for capacity adjustment, see STSI instruction in [2]. + #[display("capacity_change_reason")] + CapacityChangeReason = QC_CAPACITY_CHANGE_REASON, + + /// Capping type: "off", "soft", "hard". + #[display("capping")] + Capping = QC_CAPPING, + + /// Numeric representation of capping type, see enum #qc_cappings. + #[display("capping_num")] + CappingNum = QC_CAPPING_NUM, + + /// SSI name if part of SSI cluster. + #[display("cluster_name")] + ClusterName = QC_CLUSTER_NAME, + + /// ID of control program. + #[display("control_program_id")] + ControlProgramId = QC_CONTROL_PROGRAM_ID, + + /// CP absolute capping value. Scaled value where 0x10000 equals to one core, or 0 if no + /// capping set. + #[display("absolute_capping")] + CpAbsoluteCapping = QC_CP_ABSOLUTE_CAPPING, + + /// 1 if pool's CP virtual type has capped capacity 0 if not. See DEFINE CPUPOOL + /// command in [3]. + #[display("cp_capacity_cap")] + CpCapacityCap = QC_CP_CAPACITY_CAP, + + /// Guest current capped capacity for shared virtual CPs -- scaled value where 0x10000 + /// equals to one core; or 0 if no capping set. While this field displays the capacity, + /// either #qc_cp_capacity_cap or #qc_cp_limithard_cap must is set to indicate the kind of + /// limit. + #[display("cp_capped_capacity")] + CpCappedCapacity = QC_CP_CAPPED_CAPACITY, + + /// 1 if guest CP dispatch type has LIMITHARD capping, 0 if not. See SET SRM command + /// in [3]. + #[display("cp_dispatch_limithard")] + CpDispatchLimithard = QC_CP_DISPATCH_LIMITHARD, + + /// Dispatch type for guest CPs: 0=General Purpose (CP). + #[display("cp_dispatch_type")] + CpDispatchType = QC_CP_DISPATCH_TYPE, + + /// 1 if pool's CP virtual type has limithard capping 0 if not. See DEFINE CPUPOOL + /// command in [3]. + #[display("cp_limithard_cap")] + CpLimithardCap = QC_CP_LIMITHARD_CAP, + + /// CP weight-based capping value -- scaled value where 0x10000 equals to one core, or 0 if + /// no capping set. + #[display("cp_weight_capping")] + CpWeightCapping = QC_CP_WEIGHT_CAPPING, + + /// 1 if SRM limithard setting is consumption 0 if deadline. See SET SRM command in + /// [3]. + #[display("limithard_consumption")] + LimithardConsumption = QC_LIMITHARD_CONSUMPTION, + + /// Deprecated, see #qc_limithard_consumption. + #[display("hardlimit_consumption")] + HardlimitConsumption = QC_HARDLIMIT_CONSUMPTION, + + /// 1 if layer has multiple CPU types (e.g. CPs, IFLs, zIIPs), 0 if not. + #[display("has_multiple_cpu_types")] + HasMultipleCpuTypes = QC_HAS_MULTIPLE_CPU_TYPES, + + /// IFL absolute capping value -- scaled value where 0x10000 equals to one core, or 0 if no + /// capping set. + #[display("ifl_absolute_capping")] + IflAbsoluteCapping = QC_IFL_ABSOLUTE_CAPPING, + + /// 1 if pool's IFL virtual type has capped capacity 0 if not. See DEFINE CPUPOOL + /// command in [3]. + #[display("ifl_capacity_cap")] + IflCapacityCap = QC_IFL_CAPACITY_CAP, + + /// Guest current capped capacity for shared virtual IFLs -- scaled value where 0x10000 + /// equals to one core; or 0 if no capping set. While this field displays the capacity, + /// either #qc_ifl_capacity_cap or #qc_ifl_limithard_cap must is set to indicate the kind + /// of limit. + #[display("ifl_capped_capacity")] + IflCappedCapacity = QC_IFL_CAPPED_CAPACITY, + + /// 1 if guest IFL dispatch type has LIMITHARD capping, 0 if not. See SET SRM command + /// in [3]. + #[display("ifl_dispatch_limithard")] + IflDispatchLimithard = QC_IFL_DISPATCH_LIMITHARD, + + /// Dispatch type for guest IFLs: 0=General Purpose (CP), 3=Integrated Facility for Linux + /// (IFL). + #[display("ifl_dispatch_type")] + IflDispatchType = QC_IFL_DISPATCH_TYPE, + + /// 1 if pool's IFL virtual type has limithard capping 0 if not. See DEFINE CPUPOOL + /// command in [3]. + #[display("ifl_limithard_cap")] + IflLimithardCap = QC_IFL_LIMITHARD_CAP, + + /// IFL weight-based capping value -- scaled value where 0x10000 equals to one core, or 0 + /// if no capping set. + #[display("ifl_weight_capping")] + IflWeightCapping = QC_IFL_WEIGHT_CAPPING, + + /// zIIP absolute capping value -- scaled value where 0x10000 equals to one core, or 0 if + /// no capping set. + #[display("ziip_absolute_capping")] + ZiipAbsoluteCapping = QC_ZIIP_ABSOLUTE_CAPPING, + + /// 1 if pool's zIIP virtual type has capped capacity 0 if not. See DEFINE CPUPOOL + /// command in [3]. + #[display("ziip_capacity_cap")] + ZiipCapacityCap = QC_ZIIP_CAPACITY_CAP, + + /// Guest current capped capacity for shared virtual zIIPs -- scaled value where 0x10000 + /// equals to one core; or 0 if no capping set. While this field displays the capacity, + /// either #qc_ziip_capacity_cap or #qc_ziip_limithard_cap must is set to indicate the kind + /// of limit. + #[display("ziip_capped_capacity")] + ZiipCappedCapacity = QC_ZIIP_CAPPED_CAPACITY, + + /// 1 if guest zIIP dispatch type has LIMITHARD capping, 0 if not. See SET SRM + /// command in [3]. + #[display("ziip_dispatch_limithard")] + ZiipDispatchLimithard = QC_ZIIP_DISPATCH_LIMITHARD, + + /// Dispatch type for guest zIIPs: 0=General Purpose (CP), 5=zSeries Integrated Information + /// Processor (zIIP), ff=zIIP or CP. + #[display("ziip_dispatch_type")] + ZiipDispatchType = QC_ZIIP_DISPATCH_TYPE, + + /// 1 if pool's zIIP virtual type has limithard capping 0 if not. See DEFINE CPUPOOL + /// command in [3]. + #[display("ziip_limithard_cap")] + ZiipLimithardCap = QC_ZIIP_LIMITHARD_CAP, + + /// zIIP weight-based capping value -- scaled value where 0x10000 equals to one core, or 0 + /// if no capping set. + #[display("ziip_weight_capping")] + ZiipWeightCapping = QC_ZIIP_WEIGHT_CAPPING, + + /// Layer category, see layer tables above for details. + #[display("layer_category")] + LayerCategory = QC_LAYER_CATEGORY, + + /// Numeric representation of layer category, see enum #qc_layer_categories. + #[display("layer_category_num")] + LayerCategoryNum = QC_LAYER_CATEGORY_NUM, + + /// Guest extended name. + #[display("layer_extended_name")] + LayerExtendedName = QC_LAYER_EXTENDED_NAME, + + /// Name of container, see layer tables for details + #[display("layer_name")] + LayerName = QC_LAYER_NAME, + + /// Layer type, see layer tables above for details. + #[display("layer_type")] + LayerType = QC_LAYER_TYPE, + + /// Numeric representation of layer type, see enum #qc_layer_types. + #[display("layer_type_num")] + LayerTypeNum = QC_LAYER_TYPE_NUM, + + /// Universal unique ID. + #[display("layer_uuid")] + LayerUuid = QC_LAYER_UUID, + + /// Company that manufactured box. + #[display("manufacturer")] + Manufacturer = QC_MANUFACTURER, + + /// 1 if guest is enabled for mobility, 0 if not. + #[display("mobility_enabled")] + MobilityEnabled = QC_MOBILITY_ENABLED, + + /// Deprecated, see #qc_mobility_enabled. + #[display("mobility_eligible")] + MobilityEligible = QC_MOBILITY_ELIGIBLE, + + /// Indicates whether secure boot is available to the entity. Requires Linux kernel 5.3 or + /// later. + /// Note: This attribute is only ever available for the topmost layer. + #[display("has_secure")] + HasSecure = QC_HAS_SECURE, + + /// Indicates whether entity was booted using the secure boot feature Requires Linux kernel + /// 5.3 or later. + /// Note: This attribute is only ever available for the topmost layer. + #[display("secure")] + Secure = QC_SECURE, + + /// Model identifier, see STSI instruction in [2]. + #[display("model")] + Model = QC_MODEL, + + /// Model capacity of machine, see STSI instruction in [2]. + #[display("model_capacity")] + ModelCapacity = QC_MODEL_CAPACITY, + + /// Family of the model, enum #qc_model_families. + #[display("type_family")] + TypeFamily = QC_TYPE_FAMILY, + + /// Sum of dedicated CPs in layer. + #[display("num_cp_dedicated")] + NumCpDedicated = QC_NUM_CP_DEDICATED, + + /// Sum of shared CPs in layer. + #[display("num_cp_shared")] + NumCpShared = QC_NUM_CP_SHARED, + + /// Sum of all CPs in layer. + #[display("num_cp_total")] + NumCpTotal = QC_NUM_CP_TOTAL, + + /// Sum of configured CPs and IFLs in layer. + #[display("num_cpu_configured")] + NumCpuConfigured = QC_NUM_CPU_CONFIGURED, + + /// Sum of dedicated CPs and IFLs in layer. + #[display("num_cpu_dedicated")] + NumCpuDedicated = QC_NUM_CPU_DEDICATED, + + /// Sum of reserved CPs and IFLs in layer. + #[display("num_cpu_reserved")] + NumCpuReserved = QC_NUM_CPU_RESERVED, + + /// Sum of shared CPs and IFLs in layer. + #[display("num_cpu_shared")] + NumCpuShared = QC_NUM_CPU_SHARED, + + /// Sum of standby CPs and IFLs in layer. + #[display("num_cpu_standby")] + NumCpuStandby = QC_NUM_CPU_STANDBY, + + /// Sum of all CPs and IFLs in layer. + #[display("num_cpu_total")] + NumCpuTotal = QC_NUM_CPU_TOTAL, + + /// Sum of dedicated IFLs in layer. + #[display("num_ifl_dedicated")] + NumIflDedicated = QC_NUM_IFL_DEDICATED, + + /// Sum of shared IFLs in layer. + #[display("num_ifl_shared")] + NumIflShared = QC_NUM_IFL_SHARED, + + /// Sum of all IFLs (Integrated Facility for Linux) in layer. + #[display("num_ifl_total")] + NumIflTotal = QC_NUM_IFL_TOTAL, + + /// Sum of dedicated zIIPs in layer. + #[display("num_ziip_dedicated")] + NumZiipDedicated = QC_NUM_ZIIP_DEDICATED, + + /// Sum of shared zIIPs in layer. + #[display("num_ziip_shared")] + NumZiipShared = QC_NUM_ZIIP_SHARED, + + /// Sum of all zIIPs (Integrated Information Processor) in layer. + #[display("num_ziip_total")] + NumZiipTotal = QC_NUM_ZIIP_TOTAL, + + /// Partition characteristics, any combination of "Dedicated", "Shared" and + /// "Limited", also see STSI instruction in [2]. + #[display("partition_char")] + PartitionChar = QC_PARTITION_CHAR, + + /// Numeric representation of partition characteristics, see enum #qc_part_chars. + #[display("partition_char_num")] + PartitionCharNum = QC_PARTITION_CHAR_NUM, + + /// Partition number, see STSI instruction in [2]. + #[display("partition_number")] + PartitionNumber = QC_PARTITION_NUMBER, + + /// Identifier of the manufacturing plant, see STSI instruction in [2]. + #[display("plant")] + Plant = QC_PLANT, + + /// Secondary capability rating, see STSI instruction in [2]. + #[display("secondary_capability")] + SecondaryCapability = QC_SECONDARY_CAPABILITY, + + /// Sequence code of machine, see STSI instruction in [2]. + #[display("sequence_code")] + SequenceCode = QC_SEQUENCE_CODE, + + /// 4-digit machine type. + #[display("type")] + Type = QC_TYPE, + + /// 1 if limithard caps uses prorated core time for capping 0 if raw CPU time is used. See + /// APAR VM65680. + #[display("prorated_core_time")] + ProratedCoreTime = QC_PRORATED_CORE_TIME, + + /// Threads per CP, values >1 indicate that SMT is enabled. + #[display("num_cp_threads")] + NumCpThreads = QC_NUM_CP_THREADS, + + /// Threads per IFL, values >1 indicate that SMT is enabled. + #[display("num_ifl_threads")] + NumIflThreads = QC_NUM_IFL_THREADS, + + /// Threads per zIIP, values >1 indicate that SMT is enabled. + #[display("num_ziip_threads")] + NumZiipThreads = QC_NUM_ZIIP_THREADS, + + /// Sum of all CP and IFL cores in layer. + #[display("num_core_total")] + NumCoreTotal = QC_NUM_CORE_TOTAL, + + /// Sum of configure CP and IFL cores in layer. + #[display("num_core_configured")] + NumCoreConfigured = QC_NUM_CORE_CONFIGURED, + + /// Sum of standby CP and IFL cores in layer. + #[display("num_core_standby")] + NumCoreStandby = QC_NUM_CORE_STANDBY, + + /// Sum of reserved CP and IFL cores in layer. + #[display("num_core_reserved")] + NumCoreReserved = QC_NUM_CORE_RESERVED, + + /// Sum of dedicated CP and IFL cores in layer. + #[display("num_core_dedicated")] + NumCoreDedicated = QC_NUM_CORE_DEDICATED, + + /// Sum of shared CP and IFL cores in layer. + #[display("num_core_shared")] + NumCoreShared = QC_NUM_CORE_SHARED, + + /// Name of IBM Z model in clear text. + #[display("type_name")] + TypeName = QC_TYPE_NAME, + + /// Licensed Internal Code (LIC) level. + #[display("lic_identifier")] + LicIdentifier = QC_LIC_IDENTIFIER, + } +} diff --git a/bindings/rust/src/error.rs b/bindings/rust/src/error.rs new file mode 100644 index 00000000..2ce730c3 --- /dev/null +++ b/bindings/rust/src/error.rs @@ -0,0 +1,12 @@ +#[derive(Debug)] +pub struct Error { + pub(crate) code: i32, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "internal qclib error: {}", self.code) + } +} + +impl std::error::Error for Error {} diff --git a/bindings/rust/src/layer.rs b/bindings/rust/src/layer.rs new file mode 100644 index 00000000..ca6ab3bf --- /dev/null +++ b/bindings/rust/src/layer.rs @@ -0,0 +1,123 @@ +#[cfg(feature = "json")] +use serde_json::{Map, Value}; +use sys::*; + +use crate::enums::{AttributeId, LayerCategory, LayerType}; +use crate::layers::*; +use crate::{QueryCapacity, Result}; + +/// A handle to a system layer (e.g. CEC, LPAR, etc.). +pub struct Layer<'h> { + pub(crate) qc: &'h QueryCapacity, + pub(crate) layer: u32, +} + +impl Layer<'_> { + /// See [AttributeId::LayerTypeNum]. + pub fn layer_type_num(&self) -> Result { + Ok(self + .qc + .get_attribute_int(self.layer, AttributeId::LayerTypeNum)? + .expect("BUG: qc_layer_type_num is a common attribute")) + } + + /// See [AttributeId::LayerCategoryNum]. + pub fn layer_category_num(&self) -> Result { + Ok(self + .qc + .get_attribute_int(self.layer, AttributeId::LayerCategoryNum)? + .expect("BUG: qc_layer_category_num is a common attribute")) + } + + /// See [AttributeId::LayerType] and [LayerType]. + pub fn layer_type(&self) -> Result { + let ltype: qc_layer_type = self + .qc + .get_attribute_int(self.layer, AttributeId::LayerTypeNum)? + .expect("BUG: qc_layer_type_num is a common attribute") + .try_into() + .expect("BUG: qc_layer_type < 0"); + Ok(LayerType::from_raw(ltype)) + } + + /// See [AttributeId::LayerCategory] and [LayerCategory]. + pub fn layer_category(&self) -> Result { + let category: qc_layer_category = self + .qc + .get_attribute_int(self.layer, AttributeId::LayerCategoryNum)? + .expect("BUG: qc_layer_category_num is a common attribute") + .try_into() + .expect("BUG: qc_layer_category < 0"); + Ok(LayerCategory::from_raw(category)) + } + + impl_attr!(layer_name() -> String = LayerName); +} + +impl<'h> Layer<'h> { + /// Returns a layer-specific handle that matches the type of the layer. + pub fn details(&self) -> Result> { + let layer = match self.layer_type()? { + LayerType::CEC => LayerDetails::CEC(CEC { + qc: self.qc, + layer: self.layer, + }), + LayerType::LparGroup => todo!(), + LayerType::Lpar => LayerDetails::Lpar(Lpar { + qc: self.qc, + layer: self.layer, + }), + LayerType::ZvmHypervisor => LayerDetails::ZvmHypervisor(ZvmHypervisor { + qc: self.qc, + layer: self.layer, + }), + LayerType::ZvmCpuPool => todo!(), + LayerType::ZvmResourcePool => todo!(), + LayerType::ZvmGuest => LayerDetails::ZvmGuest(ZvmGuest { + qc: self.qc, + layer: self.layer, + }), + LayerType::KvmHypervisor => LayerDetails::KvmHypervisor(KvmHypervisor { + qc: self.qc, + layer: self.layer, + }), + LayerType::KvmGuest => LayerDetails::KvmGuest(KvmGuest { + qc: self.qc, + layer: self.layer, + }), + LayerType::ZosHypervisor => todo!(), + LayerType::ZosTenantResourceGroup => todo!(), + LayerType::ZosZcxServer => todo!(), + }; + + Ok(layer) + } + + #[cfg(feature = "json")] + pub fn to_json(&self) -> Result { + let mut map = Map::new(); + + json_pair!(map <- LayerTypeNum: int = self.layer_type_num()?); + json_pair!(map <- LayerCategoryNum: int = self.layer_category_num()?); + json_pair!(map <- LayerType: string = self.layer_type()?); + json_pair!(map <- LayerCategory: string = self.layer_category()?); + json_pair!(map <- LayerName: string? = self.layer_name()?); + + match self.details()? { + LayerDetails::CEC(cec) => cec.update_json(&mut map)?, + LayerDetails::LparGroup(_) => todo!(), + LayerDetails::Lpar(lpar) => lpar.update_json(&mut map)?, + LayerDetails::ZvmHypervisor(zvm_hypervisor) => zvm_hypervisor.update_json(&mut map)?, + LayerDetails::ZvmCpuPool(_) => todo!(), + LayerDetails::ZvmResourcePool(_) => todo!(), + LayerDetails::ZvmGuest(zvm_guest) => zvm_guest.update_json(&mut map)?, + LayerDetails::KvmHypervisor(kvm_hypervisor) => kvm_hypervisor.update_json(&mut map)?, + LayerDetails::KvmGuest(kvm_guest) => kvm_guest.update_json(&mut map)?, + LayerDetails::ZosHypervisor(_) => todo!(), + LayerDetails::ZosTenantResourceGroup(_) => todo!(), + LayerDetails::ZosZcxServer(_) => todo!(), + } + + Ok(Value::Object(map)) + } +} diff --git a/bindings/rust/src/layers/cec.rs b/bindings/rust/src/layers/cec.rs new file mode 100644 index 00000000..7c28dd1f --- /dev/null +++ b/bindings/rust/src/layers/cec.rs @@ -0,0 +1,82 @@ +use crate::{AttributeId, QueryCapacity, Result}; + +/// A handle that represents the CEC (lowest layer, i.e. physical machine). +pub struct CEC<'h> { + pub(crate) qc: &'h QueryCapacity, + pub(crate) layer: u32, +} + +impl CEC<'_> { + impl_attr!(manufacturer() -> String = Manufacturer); + impl_attr!(machine_type() -> String = Type); + impl_attr!(type_name() -> String = TypeName); + impl_attr!(type_family() -> i32 = TypeFamily); + impl_attr!(model_capacity() -> String = ModelCapacity); + impl_attr!(model() -> String = Model); + impl_attr!(sequence_code() -> String = SequenceCode); + impl_attr!(lic_identifier() -> String = LicIdentifier); + impl_attr!(plant() -> String = Plant); + impl_attr!(num_core_total() -> i32 = NumCoreTotal); + impl_attr!(num_core_configured() -> i32 = NumCoreConfigured); + impl_attr!(num_core_standby() -> i32 = NumCoreStandby); + impl_attr!(num_core_reserved() -> i32 = NumCoreReserved); + impl_attr!(num_core_dedicated() -> i32 = NumCoreDedicated); + impl_attr!(num_core_shared() -> i32 = NumCoreShared); + impl_attr!(num_cp_total() -> i32 = NumCpTotal); + impl_attr!(num_cp_dedicated() -> i32 = NumCpDedicated); + impl_attr!(num_cp_shared() -> i32 = NumCpShared); + impl_attr!(num_ifl_total() -> i32 = NumIflTotal); + impl_attr!(num_ifl_dedicated() -> i32 = NumIflDedicated); + impl_attr!(num_ifl_shared() -> i32 = NumIflShared); + impl_attr!(num_ziip_total() -> i32 = NumZiipTotal); + impl_attr!(num_ziip_dedicated() -> i32 = NumZiipDedicated); + impl_attr!(num_ziip_shared() -> i32 = NumZiipShared); + impl_attr!(num_cp_threads() -> i32 = NumCpThreads); + impl_attr!(num_ifl_threads() -> i32 = NumIflThreads); + impl_attr!(num_ziip_threads() -> i32 = NumZiipThreads); + impl_attr!(capability() -> f32 = Capability); + impl_attr!(secondary_capability() -> f32 = SecondaryCapability); + impl_attr!(capacity_adjustment_indication() -> i32 = CapacityAdjustmentIndication); + impl_attr!(capacity_change_reason() -> i32 = CapacityChangeReason); +} + +impl CEC<'_> { + #[cfg(feature = "json")] + pub(crate) fn update_json( + &self, + map: &mut serde_json::Map, + ) -> Result<()> { + json_pair!(map <- Manufacturer: string? = self.manufacturer()?); + json_pair!(map <- Type: string? = self.machine_type()?); + json_pair!(map <- TypeName: string? = self.type_name()?); + json_pair!(map <- TypeFamily: int? = self.type_family()?); + json_pair!(map <- ModelCapacity: string? = self.model_capacity()?); + json_pair!(map <- Model: string? = self.model()?); + json_pair!(map <- SequenceCode: string? = self.sequence_code()?); + json_pair!(map <- LicIdentifier: string? = self.lic_identifier()?); + json_pair!(map <- Plant: string? = self.plant()?); + json_pair!(map <- NumCoreTotal: int? = self.num_core_total()?); + json_pair!(map <- NumCoreConfigured: int? = self.num_core_configured()?); + json_pair!(map <- NumCoreStandby: int? = self.num_core_standby()?); + json_pair!(map <- NumCoreReserved: int? = self.num_core_reserved()?); + json_pair!(map <- NumCoreDedicated: int? = self.num_core_dedicated()?); + json_pair!(map <- NumCoreShared: int? = self.num_core_shared()?); + json_pair!(map <- NumCpTotal: int? = self.num_cp_total()?); + json_pair!(map <- NumCpDedicated: int? = self.num_cp_dedicated()?); + json_pair!(map <- NumCpShared: int? = self.num_cp_shared()?); + json_pair!(map <- NumIflTotal: int? = self.num_ifl_total()?); + json_pair!(map <- NumIflDedicated: int? = self.num_ifl_dedicated()?); + json_pair!(map <- NumIflShared: int? = self.num_ifl_shared()?); + json_pair!(map <- NumZiipTotal: int? = self.num_ziip_total()?); + json_pair!(map <- NumZiipDedicated: int? = self.num_ziip_dedicated()?); + json_pair!(map <- NumZiipShared: int? = self.num_ziip_shared()?); + json_pair!(map <- NumCpThreads: int? = self.num_cp_threads()?); + json_pair!(map <- NumIflThreads: int? = self.num_ifl_threads()?); + json_pair!(map <- NumZiipThreads: int? = self.num_ziip_threads()?); + json_pair!(map <- Capability: float? = self.capability()?); + json_pair!(map <- SecondaryCapability: float? = self.secondary_capability()?); + json_pair!(map <- CapacityAdjustmentIndication: int? = self.capacity_adjustment_indication()?); + json_pair!(map <- CapacityChangeReason: int? = self.capacity_change_reason()?); + Ok(()) + } +} diff --git a/bindings/rust/src/layers/kvm_guest.rs b/bindings/rust/src/layers/kvm_guest.rs new file mode 100644 index 00000000..20880c23 --- /dev/null +++ b/bindings/rust/src/layers/kvm_guest.rs @@ -0,0 +1,48 @@ +use crate::{AttributeId, QueryCapacity, Result}; + +/// A handle that represents a KVM guest. +pub struct KvmGuest<'h> { + pub(crate) qc: &'h QueryCapacity, + pub(crate) layer: u32, +} + +impl KvmGuest<'_> { + impl_attr!(extended_name() -> String = LayerExtendedName); + impl_attr!(uuid() -> String = LayerUuid); + impl_attr!(has_secure() -> i32 = HasSecure); + impl_attr!(secure() -> i32 = Secure); + impl_attr!(num_cpu_total() -> i32 = NumCoreTotal); + impl_attr!(num_cpu_configured() -> i32 = NumCoreConfigured); + impl_attr!(num_cpu_standby() -> i32 = NumCoreStandby); + impl_attr!(num_cpu_reserved() -> i32 = NumCoreReserved); + impl_attr!(num_cpu_dedicated() -> i32 = NumCoreDedicated); + impl_attr!(num_cpu_shared() -> i32 = NumCoreShared); + impl_attr!(num_ifl_total() -> i32 = NumIflTotal); + impl_attr!(num_ifl_dedicated() -> i32 = NumIflDedicated); + impl_attr!(num_ifl_shared() -> i32 = NumIflShared); + impl_attr!(ifl_dispatch_type() -> i32 = IflDispatchType); +} + +impl KvmGuest<'_> { + #[cfg(feature = "json")] + pub(crate) fn update_json( + &self, + map: &mut serde_json::Map, + ) -> Result<()> { + json_pair!(map <- LayerExtendedName: string? = self.extended_name()?); + json_pair!(map <- LayerUuid: string? = self.uuid()?); + json_pair!(map <- HasSecure: int? = self.has_secure()?); + json_pair!(map <- Secure: int? = self.secure()?); + json_pair!(map <- NumCpuTotal: int? = self.num_cpu_total()?); + json_pair!(map <- NumCpuConfigured: int? = self.num_cpu_configured()?); + json_pair!(map <- NumCpuStandby: int? = self.num_cpu_standby()?); + json_pair!(map <- NumCpuReserved: int? = self.num_cpu_reserved()?); + json_pair!(map <- NumCpuDedicated: int? = self.num_cpu_dedicated()?); + json_pair!(map <- NumCpuShared: int? = self.num_cpu_shared()?); + json_pair!(map <- NumIflTotal: int? = self.num_ifl_total()?); + json_pair!(map <- NumIflDedicated: int? = self.num_ifl_dedicated()?); + json_pair!(map <- NumIflShared: int? = self.num_ifl_shared()?); + json_pair!(map <- IflDispatchType: int? = self.ifl_dispatch_type()?); + Ok(()) + } +} diff --git a/bindings/rust/src/layers/kvm_hypervisor.rs b/bindings/rust/src/layers/kvm_hypervisor.rs new file mode 100644 index 00000000..b096417f --- /dev/null +++ b/bindings/rust/src/layers/kvm_hypervisor.rs @@ -0,0 +1,42 @@ +use crate::{AttributeId, QueryCapacity, Result}; + +/// A handle that represents a KVM hypervisor. +pub struct KvmHypervisor<'h> { + pub(crate) qc: &'h QueryCapacity, + pub(crate) layer: u32, +} + +impl KvmHypervisor<'_> { + impl_attr!(control_program_id() -> String = ControlProgramId); + impl_attr!(adjustment() -> i32 = Adjustment); + impl_attr!(num_core_total() -> i32 = NumCoreTotal); + impl_attr!(num_core_dedicated() -> i32 = NumCoreDedicated); + impl_attr!(num_core_shared() -> i32 = NumCoreShared); + impl_attr!(num_cp_total() -> i32 = NumCpTotal); + impl_attr!(num_cp_dedicated() -> i32 = NumCpDedicated); + impl_attr!(num_cp_shared() -> i32 = NumCpShared); + impl_attr!(num_ifl_total() -> i32 = NumIflTotal); + impl_attr!(num_ifl_dedicated() -> i32 = NumIflDedicated); + impl_attr!(num_ifl_shared() -> i32 = NumIflShared); +} + +impl KvmHypervisor<'_> { + #[cfg(feature = "json")] + pub(crate) fn update_json( + &self, + map: &mut serde_json::Map, + ) -> Result<()> { + json_pair!(map <- ControlProgramId: string? = self.control_program_id()?); + json_pair!(map <- Adjustment: int? = self.adjustment()?); + json_pair!(map <- NumCoreTotal: int? = self.num_core_total()?); + json_pair!(map <- NumCoreDedicated: int? = self.num_core_dedicated()?); + json_pair!(map <- NumCoreShared: int? = self.num_core_shared()?); + json_pair!(map <- NumCpTotal: int? = self.num_cp_total()?); + json_pair!(map <- NumCpDedicated: int? = self.num_cp_dedicated()?); + json_pair!(map <- NumCpShared: int? = self.num_cp_shared()?); + json_pair!(map <- NumIflTotal: int? = self.num_ifl_total()?); + json_pair!(map <- NumIflDedicated: int? = self.num_ifl_dedicated()?); + json_pair!(map <- NumIflShared: int? = self.num_ifl_shared()?); + Ok(()) + } +} diff --git a/bindings/rust/src/layers/lpar.rs b/bindings/rust/src/layers/lpar.rs new file mode 100644 index 00000000..c2f3e7fe --- /dev/null +++ b/bindings/rust/src/layers/lpar.rs @@ -0,0 +1,84 @@ +use crate::{AttributeId, QueryCapacity, Result}; + +/// A handle that represents a logical partition (LPAR). +pub struct Lpar<'h> { + pub(crate) qc: &'h QueryCapacity, + pub(crate) layer: u32, +} + +impl Lpar<'_> { + impl_attr!(extended_name() -> String = LayerExtendedName); + impl_attr!(uuid() -> String = LayerUuid); + impl_attr!(partition_number() -> i32 = PartitionNumber); + impl_attr!(partition_char() -> String = PartitionChar); + impl_attr!(partition_char_num() -> i32 = PartitionCharNum); + impl_attr!(adjustment() -> i32 = Adjustment); + impl_attr!(has_secure() -> i32 = HasSecure); + impl_attr!(secure() -> i32 = Secure); + impl_attr!(num_core_total() -> i32 = NumCoreTotal); + impl_attr!(num_core_configured() -> i32 = NumCoreConfigured); + impl_attr!(num_core_standby() -> i32 = NumCoreStandby); + impl_attr!(num_core_reserved() -> i32 = NumCoreReserved); + impl_attr!(num_core_dedicated() -> i32 = NumCoreDedicated); + impl_attr!(num_core_shared() -> i32 = NumCoreShared); + impl_attr!(num_cp_total() -> i32 = NumCpTotal); + impl_attr!(num_cp_dedicated() -> i32 = NumCpDedicated); + impl_attr!(num_cp_shared() -> i32 = NumCpShared); + impl_attr!(num_ifl_total() -> i32 = NumIflTotal); + impl_attr!(num_ifl_dedicated() -> i32 = NumIflDedicated); + impl_attr!(num_ifl_shared() -> i32 = NumIflShared); + impl_attr!(num_ziip_total() -> i32 = NumZiipTotal); + impl_attr!(num_ziip_dedicated() -> i32 = NumZiipDedicated); + impl_attr!(num_ziip_shared() -> i32 = NumZiipShared); + impl_attr!(num_cp_threads() -> i32 = NumCpThreads); + impl_attr!(num_ifl_threads() -> i32 = NumIflThreads); + impl_attr!(num_ziip_threads() -> i32 = NumZiipThreads); + impl_attr!(cp_absolute_capping() -> i32 = CpAbsoluteCapping); + impl_attr!(cp_weight_capping() -> i32 = CpWeightCapping); + impl_attr!(ifl_absolute_capping() -> i32 = IflAbsoluteCapping); + impl_attr!(ifl_weight_capping() -> i32 = IflWeightCapping); + impl_attr!(ziip_absolute_capping() -> i32 = ZiipAbsoluteCapping); + impl_attr!(ziip_weight_capping() -> i32 = ZiipWeightCapping); +} + +impl Lpar<'_> { + #[cfg(feature = "json")] + pub(crate) fn update_json( + &self, + map: &mut serde_json::Map, + ) -> Result<()> { + json_pair!(map <- LayerExtendedName: string? = self.extended_name()?); + json_pair!(map <- LayerUuid: string? = self.uuid()?); + json_pair!(map <- Adjustment: int? = self.adjustment()?); + json_pair!(map <- PartitionNumber: int? = self.partition_number()?); + json_pair!(map <- PartitionChar: string? = self.partition_char()?); + json_pair!(map <- PartitionCharNum: int? = self.partition_char_num()?); + json_pair!(map <- HasSecure: int? = self.has_secure()?); + json_pair!(map <- Secure: int? = self.secure()?); + json_pair!(map <- NumCoreTotal: int? = self.num_core_total()?); + json_pair!(map <- NumCoreConfigured: int? = self.num_core_configured()?); + json_pair!(map <- NumCoreStandby: int? = self.num_core_standby()?); + json_pair!(map <- NumCoreReserved: int? = self.num_core_reserved()?); + json_pair!(map <- NumCoreDedicated: int? = self.num_core_dedicated()?); + json_pair!(map <- NumCoreShared: int? = self.num_core_shared()?); + json_pair!(map <- NumCpTotal: int? = self.num_cp_total()?); + json_pair!(map <- NumCpDedicated: int? = self.num_cp_dedicated()?); + json_pair!(map <- NumCpShared: int? = self.num_cp_shared()?); + json_pair!(map <- NumIflTotal: int? = self.num_ifl_total()?); + json_pair!(map <- NumIflDedicated: int? = self.num_ifl_dedicated()?); + json_pair!(map <- NumIflShared: int? = self.num_ifl_shared()?); + json_pair!(map <- NumZiipTotal: int? = self.num_ziip_total()?); + json_pair!(map <- NumZiipDedicated: int? = self.num_ziip_dedicated()?); + json_pair!(map <- NumZiipShared: int? = self.num_ziip_shared()?); + json_pair!(map <- NumCpThreads: int? = self.num_cp_threads()?); + json_pair!(map <- NumIflThreads: int? = self.num_ifl_threads()?); + json_pair!(map <- NumZiipThreads: int? = self.num_ziip_threads()?); + json_pair!(map <- CpAbsoluteCapping: int? = self.cp_absolute_capping()?); + json_pair!(map <- IflAbsoluteCapping: int? = self.ifl_absolute_capping()?); + json_pair!(map <- ZiipAbsoluteCapping: int? = self.ziip_absolute_capping()?); + json_pair!(map <- CpWeightCapping: int? = self.cp_weight_capping()?); + json_pair!(map <- IflWeightCapping: int? = self.ifl_weight_capping()?); + json_pair!(map <- ZiipWeightCapping: int? = self.ziip_weight_capping()?); + Ok(()) + } +} diff --git a/bindings/rust/src/layers/lpar_group.rs b/bindings/rust/src/layers/lpar_group.rs new file mode 100644 index 00000000..0c9fef1b --- /dev/null +++ b/bindings/rust/src/layers/lpar_group.rs @@ -0,0 +1 @@ +pub struct LparGroup; diff --git a/bindings/rust/src/layers/mod.rs b/bindings/rust/src/layers/mod.rs new file mode 100644 index 00000000..a086978c --- /dev/null +++ b/bindings/rust/src/layers/mod.rs @@ -0,0 +1,59 @@ +//! Contains handles to retrive layer-specific information matching the type of the layer. +mod cec; +mod kvm_guest; +mod kvm_hypervisor; +mod lpar; +mod lpar_group; +mod zos_hypervisor; +mod zos_tenant_resource_group; +mod zos_zcx_server; +mod zvm_cpu_pool; +mod zvm_guest; +mod zvm_hypervisor; +mod zvm_resource_pool; + +pub use cec::CEC; +pub use kvm_guest::KvmGuest; +pub use kvm_hypervisor::KvmHypervisor; +pub use lpar::Lpar; +pub use lpar_group::LparGroup; +pub use zos_hypervisor::ZosHypervisor; +pub use zos_tenant_resource_group::ZosTenantResourceGroup; +pub use zos_zcx_server::ZosZcxServer; +pub use zvm_cpu_pool::ZvmCpuPool; +pub use zvm_guest::ZvmGuest; +pub use zvm_hypervisor::ZvmHypervisor; +pub use zvm_resource_pool::ZvmResourcePool; + +/// A handle to retrieve layer-specific system information. +/// +/// This struct is generally created by calling [details] on a [Layer]. +/// +/// [details]: crate::Layer::details +/// [Layer]: crate::Layer +pub enum LayerDetails<'h> { + /// CEC + CEC(CEC<'h>), + /// LPAR Capping Group + LparGroup(LparGroup), + /// LPAR + Lpar(Lpar<'h>), + /// z/VM Hypervisor + ZvmHypervisor(ZvmHypervisor<'h>), + /// z/VM CPU Pool (deprecated, use QC_LAYER_TYPE_ZVM_RESOURCE_POOL instead) + ZvmCpuPool(ZvmCpuPool), + /// z/VM Resource Pool + ZvmResourcePool(ZvmResourcePool), + /// z/VM Guest + ZvmGuest(ZvmGuest<'h>), + /// KVM Hypervisor + KvmHypervisor(KvmHypervisor<'h>), + /// KVM Guest + KvmGuest(KvmGuest<'h>), + /// z/OS Hypervisor + ZosHypervisor(ZosHypervisor), + /// z/OS Tenant Resource Group + ZosTenantResourceGroup(ZosTenantResourceGroup), + /// z/OS cCX Server + ZosZcxServer(ZosZcxServer), +} diff --git a/bindings/rust/src/layers/zos_hypervisor.rs b/bindings/rust/src/layers/zos_hypervisor.rs new file mode 100644 index 00000000..7ea661ba --- /dev/null +++ b/bindings/rust/src/layers/zos_hypervisor.rs @@ -0,0 +1 @@ +pub struct ZosHypervisor; diff --git a/bindings/rust/src/layers/zos_tenant_resource_group.rs b/bindings/rust/src/layers/zos_tenant_resource_group.rs new file mode 100644 index 00000000..5b83b864 --- /dev/null +++ b/bindings/rust/src/layers/zos_tenant_resource_group.rs @@ -0,0 +1 @@ +pub struct ZosTenantResourceGroup; diff --git a/bindings/rust/src/layers/zos_zcx_server.rs b/bindings/rust/src/layers/zos_zcx_server.rs new file mode 100644 index 00000000..99754e45 --- /dev/null +++ b/bindings/rust/src/layers/zos_zcx_server.rs @@ -0,0 +1 @@ +pub struct ZosZcxServer; diff --git a/bindings/rust/src/layers/zvm_cpu_pool.rs b/bindings/rust/src/layers/zvm_cpu_pool.rs new file mode 100644 index 00000000..a0464bcf --- /dev/null +++ b/bindings/rust/src/layers/zvm_cpu_pool.rs @@ -0,0 +1 @@ +pub struct ZvmCpuPool; diff --git a/bindings/rust/src/layers/zvm_guest.rs b/bindings/rust/src/layers/zvm_guest.rs new file mode 100644 index 00000000..7c669cf0 --- /dev/null +++ b/bindings/rust/src/layers/zvm_guest.rs @@ -0,0 +1,80 @@ +use crate::{AttributeId, QueryCapacity, Result}; + +/// A handle that represents a z/VM guest. +pub struct ZvmGuest<'h> { + pub(crate) qc: &'h QueryCapacity, + pub(crate) layer: u32, +} + +impl ZvmGuest<'_> { + impl_attr!(capping() -> String = Capping); + impl_attr!(capping_num() -> i32 = CappingNum); + impl_attr!(mobility_enabled() -> i32 = MobilityEnabled); + impl_attr!(has_secure() -> i32 = HasSecure); + impl_attr!(secure() -> i32 = Secure); + impl_attr!(num_cpu_total() -> i32 = NumCpuTotal); + impl_attr!(num_cpu_configured() -> i32 = NumCpuConfigured); + impl_attr!(num_cpu_standby() -> i32 = NumCpuStandby); + impl_attr!(num_cpu_reserved() -> i32 = NumCpuReserved); + impl_attr!(num_cpu_dedicated() -> i32 = NumCpuDedicated); + impl_attr!(num_cpu_shared() -> i32 = NumCpuShared); + impl_attr!(num_cp_total() -> i32 = NumCpTotal); + impl_attr!(num_cp_dedicated() -> i32 = NumCpDedicated); + impl_attr!(num_cp_shared() -> i32 = NumCpShared); + impl_attr!(num_ifl_total() -> i32 = NumIflTotal); + impl_attr!(num_ifl_dedicated() -> i32 = NumIflDedicated); + impl_attr!(num_ifl_shared() -> i32 = NumIflShared); + impl_attr!(num_ziip_total() -> i32 = NumZiipTotal); + impl_attr!(num_ziip_dedicated() -> i32 = NumZiipDedicated); + impl_attr!(num_ziip_shared() -> i32 = NumZiipShared); + impl_attr!(has_multiple_cpu_types() -> i32 = HasMultipleCpuTypes); + impl_attr!(cp_dispatch_limithard() -> i32 = CpDispatchLimithard); + impl_attr!(cp_dispatch_type() -> i32 = CpDispatchType); + impl_attr!(cp_capped_capacity() -> i32 = CpCappedCapacity); + impl_attr!(ifl_dispatch_limithard() -> i32 = IflDispatchLimithard); + impl_attr!(ifl_dispatch_type() -> i32 = IflDispatchType); + impl_attr!(ifl_capped_capacity() -> i32 = IflCappedCapacity); + impl_attr!(ziip_dispatch_limithard() -> i32 = ZiipDispatchLimithard); + impl_attr!(ziip_dispatch_type() -> i32 = ZiipDispatchType); + impl_attr!(ziip_capped_capacity() -> i32 = ZiipCappedCapacity); +} + +impl ZvmGuest<'_> { + #[cfg(feature = "json")] + pub(crate) fn update_json( + &self, + map: &mut serde_json::Map, + ) -> Result<()> { + json_pair!(map <- Capping: string? = self.capping()?); + json_pair!(map <- CappingNum: int? = self.capping_num()?); + json_pair!(map <- MobilityEnabled: int? = self.mobility_enabled()?); + json_pair!(map <- HasSecure: int? = self.has_secure()?); + json_pair!(map <- Secure: int? = self.secure()?); + json_pair!(map <- NumCpuTotal: int? = self.num_cpu_total()?); + json_pair!(map <- NumCpuConfigured: int? = self.num_cpu_configured()?); + json_pair!(map <- NumCpuStandby: int? = self.num_cpu_standby()?); + json_pair!(map <- NumCpuReserved: int? = self.num_cpu_reserved()?); + json_pair!(map <- NumCpuDedicated: int? = self.num_cpu_dedicated()?); + json_pair!(map <- NumCpuShared: int? = self.num_cpu_shared()?); + json_pair!(map <- NumCpTotal: int? = self.num_cp_total()?); + json_pair!(map <- NumCpDedicated: int? = self.num_cp_dedicated()?); + json_pair!(map <- NumCpShared: int? = self.num_cp_shared()?); + json_pair!(map <- NumIflTotal: int? = self.num_ifl_total()?); + json_pair!(map <- NumIflDedicated: int? = self.num_ifl_dedicated()?); + json_pair!(map <- NumIflShared: int? = self.num_ifl_shared()?); + json_pair!(map <- NumZiipTotal: int? = self.num_ziip_total()?); + json_pair!(map <- NumZiipDedicated: int? = self.num_ziip_dedicated()?); + json_pair!(map <- NumZiipShared: int? = self.num_ziip_shared()?); + json_pair!(map <- HasMultipleCpuTypes: int? = self.has_multiple_cpu_types()?); + json_pair!(map <- CpDispatchLimithard: int? = self.cp_dispatch_limithard()?); + json_pair!(map <- CpCappedCapacity: int? = self.cp_capped_capacity()?); + json_pair!(map <- IflDispatchLimithard: int? = self.ifl_dispatch_limithard()?); + json_pair!(map <- IflCappedCapacity: int? = self.ifl_capped_capacity()?); + json_pair!(map <- ZiipDispatchLimithard: int? = self.ziip_dispatch_limithard()?); + json_pair!(map <- ZiipCappedCapacity: int? = self.ziip_capped_capacity()?); + json_pair!(map <- CpDispatchType: int? = self.cp_dispatch_type()?); + json_pair!(map <- IflDispatchType: int? = self.ifl_dispatch_type()?); + json_pair!(map <- ZiipDispatchType: int? = self.ziip_dispatch_type()?); + Ok(()) + } +} diff --git a/bindings/rust/src/layers/zvm_hypervisor.rs b/bindings/rust/src/layers/zvm_hypervisor.rs new file mode 100644 index 00000000..a4080271 --- /dev/null +++ b/bindings/rust/src/layers/zvm_hypervisor.rs @@ -0,0 +1,60 @@ +use crate::{AttributeId, QueryCapacity, Result}; + +/// A handle that represents a z/VM hypervisor. +pub struct ZvmHypervisor<'h> { + pub(crate) qc: &'h QueryCapacity, + pub(crate) layer: u32, +} + +impl ZvmHypervisor<'_> { + impl_attr!(cluster_name() -> String = ClusterName); + impl_attr!(control_program_id() -> String = ControlProgramId); + impl_attr!(adjustment() -> i32 = Adjustment); + impl_attr!(limithard_consumption() -> i32 = LimithardConsumption); + impl_attr!(prorated_core_time() -> i32 = ProratedCoreTime); + impl_attr!(num_core_total() -> i32 = NumCoreTotal); + impl_attr!(num_core_dedicated() -> i32 = NumCoreDedicated); + impl_attr!(num_core_shared() -> i32 = NumCoreShared); + impl_attr!(num_cp_total() -> i32 = NumCpTotal); + impl_attr!(num_cp_dedicated() -> i32 = NumCpDedicated); + impl_attr!(num_cp_shared() -> i32 = NumCpShared); + impl_attr!(num_ifl_total() -> i32 = NumIflTotal); + impl_attr!(num_ifl_dedicated() -> i32 = NumIflDedicated); + impl_attr!(num_ifl_shared() -> i32 = NumIflShared); + impl_attr!(num_ziip_total() -> i32 = NumZiipTotal); + impl_attr!(num_ziip_dedicated() -> i32 = NumZiipDedicated); + impl_attr!(num_ziip_shared() -> i32 = NumZiipShared); + impl_attr!(num_cp_threads() -> i32 = NumCpThreads); + impl_attr!(num_ifl_threads() -> i32 = NumIflThreads); + impl_attr!(num_ziip_threads() -> i32 = NumZiipThreads); +} + +impl ZvmHypervisor<'_> { + #[cfg(feature = "json")] + pub(crate) fn update_json( + &self, + map: &mut serde_json::Map, + ) -> Result<()> { + json_pair!(map <- ClusterName: string? = self.cluster_name()?); + json_pair!(map <- ControlProgramId: string? = self.control_program_id()?); + json_pair!(map <- Adjustment: int? = self.adjustment()?); + json_pair!(map <- LimithardConsumption: int? = self.limithard_consumption()?); + json_pair!(map <- ProratedCoreTime: int? = self.prorated_core_time()?); + json_pair!(map <- NumCoreTotal: int? = self.num_core_total()?); + json_pair!(map <- NumCoreDedicated: int? = self.num_core_dedicated()?); + json_pair!(map <- NumCoreShared: int? = self.num_core_shared()?); + json_pair!(map <- NumCpTotal: int? = self.num_cp_total()?); + json_pair!(map <- NumCpDedicated: int? = self.num_cp_dedicated()?); + json_pair!(map <- NumCpShared: int? = self.num_cp_shared()?); + json_pair!(map <- NumIflTotal: int? = self.num_ifl_total()?); + json_pair!(map <- NumIflDedicated: int? = self.num_ifl_dedicated()?); + json_pair!(map <- NumIflShared: int? = self.num_ifl_shared()?); + json_pair!(map <- NumZiipTotal: int? = self.num_ziip_total()?); + json_pair!(map <- NumZiipDedicated: int? = self.num_ziip_dedicated()?); + json_pair!(map <- NumZiipShared: int? = self.num_ziip_shared()?); + json_pair!(map <- NumCpThreads: int? = self.num_cp_threads()?); + json_pair!(map <- NumIflThreads: int? = self.num_ifl_threads()?); + json_pair!(map <- NumZiipThreads: int? = self.num_ziip_threads()?); + Ok(()) + } +} diff --git a/bindings/rust/src/layers/zvm_resource_pool.rs b/bindings/rust/src/layers/zvm_resource_pool.rs new file mode 100644 index 00000000..7fed6541 --- /dev/null +++ b/bindings/rust/src/layers/zvm_resource_pool.rs @@ -0,0 +1 @@ +pub struct ZvmResourcePool; diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 00000000..897af616 --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -0,0 +1,193 @@ +//! Rust bindings and idiomatic interface to [`qclib`]. +//! +//! `qclib` provides an API and associated utilities for extraction of system information for Linux +//! on IBM Z. +//! +//! For instance, it will provide the number of CPUs +//! +//! * on the machine (**CEC**, Central Electronic Complex) layer +//! * in **PR/SM** (Processor Resource/Systems Manager), i.e. visible to LPARs, including LPAR +//! groups +//! * in **z/VM** hosts, guests and resource pools +//! * in **KVM** hosts and guests +//! * in **zCX** hosts, tenant resource groups and servers +//! +//! This allows calculation of the upper limit of CPU resources a highest level guest can use. +//! +//! E.g.: If an LPAR on a z13 provides 4 CPUs to a z/VM hypervisor, and the hypervisor provides 8 +//! virtual CPUs to a guest, `qclib` can be used to retrieve all of these numbers, and it can be +//! concluded that not more capacity than 4 CPUs can be used by the software running in the guest. +//! +//! [`qclib`]: https://github.com/ibm-s390-linux/qclib +#[macro_use] +mod macros; + +mod enums; +mod error; +mod layer; +pub mod layers; + +#[cfg(feature = "json")] +use serde_json::{Map, Value}; +use sys::*; + +pub use enums::{AttributeId, LayerCategory, LayerType}; +pub use error::Error; +pub use layer::Layer; +pub use layers::LayerDetails; + +type Result = std::result::Result; + +/// Opaque handle to the underlying sources of system information provided by [qclib]. +/// +/// [qclib]: https://github.com/ibm-s390-linux/qclib/ +pub struct QueryCapacity { + handle: *mut ::std::os::raw::c_void, +} + +impl QueryCapacity { + /// Create and return a new handle by attaching to the system information sources for + /// extracting system information. + pub fn new() -> Result { + let mut rc = 0; + let handle = unsafe { qc_open(&mut rc) }; + + if rc < 0 { + return Err(Error { code: rc }); + } + + Ok(Self { handle }) + } + + /// Returns an iterator over all available layers. + /// + /// Layers are iterated over from bottom to top, i.e. starting from the CEC to the current + /// layer. + pub fn layers(&self) -> Result> { + Ok(LayersIter { + qc: self, + layer: 0, + max_layer: self.get_num_layers()?, + }) + } + + /// Returns a JSON object containing available layers with their attributes. + #[cfg(feature = "json")] + pub fn to_json(&self) -> Result { + let map = self + .layers()? + .enumerate() + .map(|(n, layer)| Ok((format!("Layer {n}"), layer.to_json()?))) + .collect::>>()?; + Ok(Value::Object(map)) + } +} + +impl QueryCapacity { + fn get_num_layers(&self) -> Result { + let mut rc = 0; + let layers = unsafe { sys::qc_get_num_layers(self.as_raw(), &mut rc) }; + + if rc < 0 { + return Err(Error { code: rc }); + } + + Ok(layers as u32) + } + + fn get_attribute_string(&self, layer: u32, id: AttributeId) -> Result> { + let mut value: *const std::os::raw::c_char = std::ptr::null_mut(); + let rc = unsafe { + qc_get_attribute_string(self.as_raw(), id.as_raw(), layer as i32, &mut value) + }; + + match rc { + 1 => Ok(Some( + unsafe { std::ffi::CStr::from_ptr(value) } + .to_string_lossy() + .to_string(), + )), + 0 => Ok(None), + _ => Err(Error { code: rc }), + } + } + + fn get_attribute_int(&self, layer: u32, id: AttributeId) -> Result> { + let mut value = 0; + let rc = + unsafe { qc_get_attribute_int(self.as_raw(), id.as_raw(), layer as i32, &mut value) }; + + match rc { + 1 => Ok(Some(value)), + 0 => Ok(None), + _ => Err(Error { code: rc }), + } + } + + fn get_attribute_float(&self, layer: u32, id: AttributeId) -> Result> { + let mut value = 0.0; + let rc = + unsafe { qc_get_attribute_float(self.as_raw(), id.as_raw(), layer as i32, &mut value) }; + + match rc { + 1 => Ok(Some(value)), + 0 => Ok(None), + _ => Err(Error { code: rc }), + } + } + + fn as_raw(&self) -> *mut ::std::os::raw::c_void { + self.handle + } +} + +impl Drop for QueryCapacity { + fn drop(&mut self) { + unsafe { + qc_close(self.handle); + } + } +} + +/// An iterator over layers. +/// +/// This struct is created with the [`layers`] method on [`QueryCapacity`]. See it's documentation +/// for more. +/// +/// [`layers`]: QueryCapacity::layers +pub struct LayersIter<'h> { + qc: &'h QueryCapacity, + layer: u32, + max_layer: u32, +} + +impl<'h> Iterator for LayersIter<'h> { + type Item = Layer<'h>; + + fn next(&mut self) -> Option { + if self.layer == self.max_layer { + return None; + } + + let layer = Layer { + qc: self.qc, + layer: self.layer, + }; + self.layer += 1; + Some(layer) + } +} + +impl DoubleEndedIterator for LayersIter<'_> { + fn next_back(&mut self) -> Option { + if self.layer == self.max_layer { + return None; + } + + self.layer += 1; + Some(Layer { + qc: self.qc, + layer: self.max_layer - self.layer, + }) + } +} diff --git a/bindings/rust/src/macros.rs b/bindings/rust/src/macros.rs new file mode 100644 index 00000000..9c99f334 --- /dev/null +++ b/bindings/rust/src/macros.rs @@ -0,0 +1,75 @@ +macro_rules! impl_attr { + ($f:ident() -> String = $attr:ident) => { + #[doc = concat!("See [AttributeId::", stringify!($attr), "].")] + pub fn $f(&self) -> Result> { + self.qc.get_attribute_string(self.layer, AttributeId::$attr) + } + }; + ($f:ident() -> i32 = $attr:ident) => { + #[doc = concat!("See [AttributeId::", stringify!($attr), "].")] + pub fn $f(&self) -> Result> { + self.qc.get_attribute_int(self.layer, AttributeId::$attr) + } + }; + ($f:ident() -> f32 = $attr:ident) => { + #[doc = concat!("See [AttributeId::", stringify!($attr), "].")] + pub fn $f(&self) -> Result> { + self.qc.get_attribute_float(self.layer, AttributeId::$attr) + } + }; +} + +#[cfg(feature = "json")] +macro_rules! json_pair { + ($map:ident <- $attr:ident: string = $v:expr) => { + $map.insert( + AttributeId::$attr.to_string(), + ::serde_json::Value::String($v.to_string()), + ) + }; + ($map:ident <- $attr:ident: string? = $v:expr) => { + $map.insert( + AttributeId::$attr.to_string(), + match $v { + Some(v) => ::serde_json::Value::String(v.to_string()), + None => ::serde_json::Value::Null, + }, + ) + }; + ($map:ident <- $attr:ident: int = $v:expr) => { + $map.insert( + AttributeId::$attr.to_string(), + ::serde_json::Value::Number($v.into()), + ) + }; + ($map:ident <- $attr:ident: int? = $v:expr) => { + $map.insert( + AttributeId::$attr.to_string(), + match $v { + Some(v) => ::serde_json::Value::Number(v.into()), + None => ::serde_json::Value::Null, + }, + ) + }; + ($map:ident <- $attr:ident: float = $v:expr) => { + $map.insert( + AttributeId::$attr.to_string(), + ::serde_json::Value::Number( + ::serde_json::Number::from_f64($v.into()) + .unwrap_or_else(|| panic!("BUG: {} is NaN: {:?}", AttributeId::$attr, v)), + ), + ) + }; + ($map:ident <- $attr:ident: float? = $v:expr) => { + $map.insert( + AttributeId::$attr.to_string(), + match $v { + Some(v) => ::serde_json::Value::Number( + ::serde_json::Number::from_f64(v.into()) + .unwrap_or_else(|| panic!("BUG: {} is NaN: {:?}", AttributeId::$attr, v)), + ), + None => ::serde_json::Value::Null, + }, + ) + }; +} From 3c41a1721023fa7002440dd0fb6f056679ffcbf1 Mon Sep 17 00:00:00 2001 From: Bjoern Walk Date: Thu, 3 Jul 2025 09:54:41 +0200 Subject: [PATCH 2/2] bindings: add examples for Rust bindings Add native Rust implementations of zhypinfo and zname as an example for the usage of the bindings. Signed-off-by: Bjoern Walk --- bindings/rust/Cargo.lock | 200 +++++++++++++++++++++++++++++ bindings/rust/Cargo.toml | 3 + bindings/rust/examples/zhypinfo.rs | 171 ++++++++++++++++++++++++ bindings/rust/examples/zname.rs | 91 +++++++++++++ 4 files changed, 465 insertions(+) create mode 100644 bindings/rust/examples/zhypinfo.rs create mode 100644 bindings/rust/examples/zname.rs diff --git a/bindings/rust/Cargo.lock b/bindings/rust/Cargo.lock index ff9cc411..12fbb955 100644 --- a/bindings/rust/Cargo.lock +++ b/bindings/rust/Cargo.lock @@ -2,6 +2,114 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[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" @@ -14,6 +122,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -27,6 +141,7 @@ dependencies = [ name = "qclib" version = "2.0.0" dependencies = [ + "clap", "qclib-sys", "serde_json", ] @@ -82,6 +197,12 @@ dependencies = [ "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.104" @@ -98,3 +219,82 @@ 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/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index a2575437..96263510 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -20,3 +20,6 @@ json = ["dep:serde_json"] package = "qclib-sys" path = "qclib-sys" version = "2" + +[dev-dependencies] +clap = { version = "4.5", features = ["derive"] } diff --git a/bindings/rust/examples/zhypinfo.rs b/bindings/rust/examples/zhypinfo.rs new file mode 100644 index 00000000..9c879f97 --- /dev/null +++ b/bindings/rust/examples/zhypinfo.rs @@ -0,0 +1,171 @@ +use clap::Parser; +use qclib::{LayerCategory, LayerDetails, QueryCapacity}; + +type Error = Box; + +#[derive(Debug, Parser)] +struct Args { + #[arg(short, long)] + json: bool, + + #[arg(short, long)] + layers: bool, + + #[arg(short = 'L', long)] + levels: bool, +} + +impl Args { + fn validate(self) -> Result { + if self.json && self.layers || self.layers && self.levels || self.levels && self.json { + Err("Error: Only one of options --json, --layers and --levels is allowed".into()) + } else { + Ok(self) + } + } +} + +fn print_json(qc: QueryCapacity) -> Result<(), Error> { + println!("{}", serde_json::to_string_pretty(&qc.to_json()?)?); + Ok(()) +} + +fn print_layer_count(qc: QueryCapacity) -> Result<(), Error> { + let layers = qc.layers()?.count(); + println!("{layers}"); + Ok(()) +} + +fn print_level_count(qc: QueryCapacity) -> Result<(), Error> { + let levels = qc + .layers()? + .filter(|layer| layer.layer_category().unwrap() == LayerCategory::Host) + .count(); + println!("{levels}"); + Ok(()) +} + +fn print_layers(qc: QueryCapacity) -> Result<(), Error> { + let mut level = -1; + + let infos = qc + .layers()? + .enumerate() + .map(|(n, layer)| { + if layer.layer_category()? == LayerCategory::Host { + level += 1; + } + + let (ifl, cp, total) = match layer.details()? { + LayerDetails::CEC(cec) => { + let ifl = cec.num_ifl_total()?; + let cp = cec.num_cp_total()?; + let total = match (cec.num_core_dedicated()?, cec.num_core_shared()?) { + (Some(ifl), Some(cp)) => Some(ifl + cp), + _ => None, + }; + (ifl, cp, total) + } + LayerDetails::LparGroup(_) => todo!(), + LayerDetails::Lpar(lpar) => { + let ifl = lpar.num_ifl_total()?; + let cp = lpar.num_cp_total()?; + let total = match (ifl, cp) { + (Some(ifl), Some(cp)) => Some(ifl + cp), + _ => None, + }; + (ifl, cp, total) + } + LayerDetails::ZvmHypervisor(hypervisor) => { + let ifl = hypervisor.num_ifl_total()?; + let cp = hypervisor.num_cp_total()?; + let total = hypervisor.num_core_total()?; + (ifl, cp, total) + } + LayerDetails::ZvmCpuPool(_) => todo!(), + LayerDetails::ZvmResourcePool(_) => todo!(), + LayerDetails::ZvmGuest(guest) => { + let ifl = guest.num_ifl_total()?; + let cp = guest.num_cp_total()?; + let total = guest.num_cpu_total()?; + (ifl, cp, total) + } + LayerDetails::KvmHypervisor(hypervisor) => { + let ifl = hypervisor.num_ifl_total()?; + let cp = hypervisor.num_cp_total()?; + let total = hypervisor.num_core_total()?; + (ifl, cp, total) + } + LayerDetails::KvmGuest(guest) => { + let ifl = guest.num_ifl_total()?; + (ifl, Some(0), ifl) + } + LayerDetails::ZosHypervisor(_) => todo!(), + LayerDetails::ZosTenantResourceGroup(_) => todo!(), + LayerDetails::ZosZcxServer(_) => todo!(), + }; + + let layer_type = layer.layer_type()?; + let category = layer.layer_category()?; + let name = layer.layer_name()?.unwrap_or("-".to_string()); + let ifl = ifl.map(|v| format!("{v}")).unwrap_or("-".to_string()); + let cp = cp.map(|v| format!("{v}")).unwrap_or("-".to_string()); + let total = total.map(|v| format!("{v}")).unwrap_or("-".to_string()); + + let fields = [ + format!("{n:3}"), + format!("{layer_type:26}"), + format!("{level:3}"), + format!("{category:5}"), + format!("{name:8}"), + format!("{ifl:>5}"), + format!("{cp:>5}"), + format!("{total:>5}"), + ]; + + Ok(fields.join(" ")) + }) + .collect::, Error>>()?; + + println!(" # Layer_Type Lvl Categ Name IFLs CPs Total"); + println!("--------------------------------------------------------------------------"); + + for info in infos.iter().rev() { + println!("{info}"); + } + + Ok(()) +} + +fn main() { + let args = match Args::parse().validate() { + Ok(args) => args, + Err(err) => { + eprintln!("{err}"); + std::process::exit(1); + } + }; + + let qc = match QueryCapacity::new() { + Ok(qc) => qc, + Err(err) => { + eprintln!("Error: Could not open capacity data: {err}"); + std::process::exit(1); + } + }; + + if let Err(err) = { + if args.json { + print_json(qc) + } else if args.layers { + print_layer_count(qc) + } else if args.levels { + print_level_count(qc) + } else { + print_layers(qc) + } + } { + eprintln!("{err}"); + std::process::exit(1); + } +} diff --git a/bindings/rust/examples/zname.rs b/bindings/rust/examples/zname.rs new file mode 100644 index 00000000..921b9d56 --- /dev/null +++ b/bindings/rust/examples/zname.rs @@ -0,0 +1,91 @@ +use clap::Parser; +use qclib::{LayerDetails, LayerType, QueryCapacity}; + +type Error = Box; + +#[derive(Debug, Parser)] +struct Args { + #[arg(short, long)] + all: bool, + + #[arg(short, long)] + capacity: bool, + + #[arg(short = 'i', long)] + cpuid: bool, + + #[arg(short = 'u', long)] + manufacturer: bool, + + #[arg(short, long)] + model: bool, + + #[arg(short, long)] + name: bool, +} + +impl Args { + fn defaults(mut self) -> Self { + if !self.all + && !self.capacity + && !self.cpuid + && !self.manufacturer + && !self.model + && !self.name + { + self.name = true; + } + self + } +} + +fn print_model_info(args: Args) -> Result<(), Error> { + let qc = QueryCapacity::new()?; + let Some(layer) = qc + .layers()? + .find(|layer| matches!(layer.layer_type(), Ok(LayerType::CEC))) + else { + return Err("Error: Could not retrieve CEC information".into()); + }; + let LayerDetails::CEC(cec) = layer.details()? else { + unreachable!(); + }; + + let name = cec.type_name()?.unwrap_or("".to_string()); + let cpuid = cec.machine_type()?.unwrap_or("".to_string()); + let manufacturer = cec.manufacturer()?.unwrap_or("".to_string()); + let model_capacity = cec.model_capacity()?.unwrap_or("".to_string()); + let model = cec.model()?.unwrap_or("".to_string()); + + if args.all { + println!("{name} {model} {model_capacity} {cpuid} {manufacturer}"); + } else { + if args.name { + print!("{name} "); + } + if args.model { + print!("{model} "); + } + if args.capacity { + print!("{model_capacity} "); + } + if args.cpuid { + print!("{cpuid} "); + } + if args.manufacturer { + print!("{manufacturer} "); + } + println!(); + } + + Ok(()) +} + +fn main() { + let args = Args::parse().defaults(); + + if let Err(err) = print_model_info(args) { + eprintln!("{err}"); + std::process::exit(1); + } +}