diff --git a/api/resource/definitions/block/block.proto b/api/resource/definitions/block/block.proto
index 1a774d10439..10ef0b4659a 100755
--- a/api/resource/definitions/block/block.proto
+++ b/api/resource/definitions/block/block.proto
@@ -243,3 +243,17 @@ message VolumeStatusSpec {
string parent_id = 19;
}
+// ZswapStatusSpec is the spec for ZswapStatus resource.
+message ZswapStatusSpec {
+ uint64 total_size_bytes = 1;
+ string total_size_human = 2;
+ uint64 stored_pages = 3;
+ uint64 pool_limit_hit = 4;
+ uint64 reject_reclaim_fail = 5;
+ uint64 reject_alloc_fail = 6;
+ uint64 reject_kmemcache_fail = 7;
+ uint64 reject_compress_fail = 8;
+ uint64 reject_compress_poor = 9;
+ uint64 written_back_pages = 10;
+}
+
diff --git a/cmd/talosctl/cmd/talos/cgroupsprinter/presets/swap.yaml b/cmd/talosctl/cmd/talos/cgroupsprinter/presets/swap.yaml
index 6ebcded28fc..7133299feda 100644
--- a/cmd/talosctl/cmd/talos/cgroupsprinter/presets/swap.yaml
+++ b/cmd/talosctl/cmd/talos/cgroupsprinter/presets/swap.yaml
@@ -7,3 +7,9 @@ columns:
template: '{{ .MemorySwapHigh.HumanizeIBytes | printf "%8s" }}'
- name: SwapMax
template: '{{ .MemorySwapMax.HumanizeIBytes | printf "%8s" }}'
+ - name: ZswapCurrent
+ template: '{{ .MemoryZswapCurrent.HumanizeIBytes | printf "%8s" }}'
+ - name: ZswapMax
+ template: '{{ .MemoryZswapMax.HumanizeIBytes | printf "%8s" }}'
+ - name: ZswapWriteback
+ template: '{{ .MemoryZswapWriteback }}'
diff --git a/hack/release.toml b/hack/release.toml
index b82bb8a6288..fad736ee00e 100644
--- a/hack/release.toml
+++ b/hack/release.toml
@@ -52,6 +52,13 @@ This feature can be enable by using [SwapVolumeConfig](https://www.talos.dev/v1.
title = "VMware"
description = """\
Talos VMWare platform now supports `arm64` architecture in addition to `amd64`.
+"""
+
+ [notes.zswap]
+ title = "Zswap Support"
+ description = """\
+Talos now supports zswap, a compressed cache for swap pages.
+This feature can be enabled by using [ZswapConfig](https://www.talos.dev/v1.11/reference/configuration/block/zswapconfig/) document in the machine configuration.
"""
[make_deps]
diff --git a/hack/test/patches/ephemeral-nvme.yaml b/hack/test/patches/ephemeral-nvme.yaml
index e0df1a7262d..8eb4c385e05 100644
--- a/hack/test/patches/ephemeral-nvme.yaml
+++ b/hack/test/patches/ephemeral-nvme.yaml
@@ -21,3 +21,8 @@ machine:
extraConfig:
memorySwap:
swapBehavior: LimitedSwap
+---
+apiVersion: v1alpha1
+kind: ZswapConfig
+maxPoolPercent: 25
+shrinkerEnabled: true
diff --git a/internal/app/machined/pkg/controllers/block/zswap_config.go b/internal/app/machined/pkg/controllers/block/zswap_config.go
new file mode 100644
index 00000000000..91eec9f60d8
--- /dev/null
+++ b/internal/app/machined/pkg/controllers/block/zswap_config.go
@@ -0,0 +1,115 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package block
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+
+ "github.com/cosi-project/runtime/pkg/controller"
+ "github.com/cosi-project/runtime/pkg/safe"
+ "github.com/cosi-project/runtime/pkg/state"
+ "github.com/siderolabs/gen/optional"
+ "go.uber.org/zap"
+
+ configconfig "github.com/siderolabs/talos/pkg/machinery/config/config"
+ "github.com/siderolabs/talos/pkg/machinery/resources/config"
+ "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
+)
+
+// ZswapConfigController provides zswap configuration based machine configuration.
+type ZswapConfigController struct{}
+
+// Name implements controller.Controller interface.
+func (ctrl *ZswapConfigController) Name() string {
+ return "block.ZswapConfigController"
+}
+
+// Inputs implements controller.Controller interface.
+func (ctrl *ZswapConfigController) Inputs() []controller.Input {
+ return []controller.Input{
+ {
+ Namespace: config.NamespaceName,
+ Type: config.MachineConfigType,
+ ID: optional.Some(config.ActiveID),
+ Kind: controller.InputWeak,
+ },
+ }
+}
+
+// Outputs implements controller.Controller interface.
+func (ctrl *ZswapConfigController) Outputs() []controller.Output {
+ return []controller.Output{
+ {
+ Type: runtime.KernelParamSpecType,
+ Kind: controller.OutputShared,
+ },
+ }
+}
+
+// Run implements controller.Controller interface.
+//
+//nolint:gocyclo
+func (ctrl *ZswapConfigController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error {
+ for {
+ select {
+ case <-r.EventCh():
+ case <-ctx.Done():
+ return nil
+ }
+
+ // load config if present
+ cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.ActiveID)
+ if err != nil && !state.IsNotFoundError(err) {
+ return fmt.Errorf("error fetching machine configuration")
+ }
+
+ r.StartTrackingOutputs()
+
+ var zswapCfg configconfig.ZswapConfig
+
+ if cfg != nil {
+ zswapCfg = cfg.Config().ZswapConfig()
+ }
+
+ if zswapCfg != nil { // enabled
+ if err := safe.WriterModify(ctx, r, runtime.NewKernelParamSpec(runtime.NamespaceName, "sys.module.zswap.parameters.enabled"),
+ func(p *runtime.KernelParamSpec) error {
+ p.TypedSpec().Value = "Y"
+
+ return nil
+ }); err != nil {
+ return fmt.Errorf("error setting zswap config: %w", err)
+ }
+
+ if err := safe.WriterModify(ctx, r, runtime.NewKernelParamSpec(runtime.NamespaceName, "sys.module.zswap.parameters.max_pool_percent"),
+ func(p *runtime.KernelParamSpec) error {
+ p.TypedSpec().Value = strconv.Itoa(zswapCfg.MaxPoolPercent())
+
+ return nil
+ }); err != nil {
+ return fmt.Errorf("error setting zswap config: %w", err)
+ }
+
+ if err := safe.WriterModify(ctx, r, runtime.NewKernelParamSpec(runtime.NamespaceName, "sys.module.zswap.parameters.shrinker_enabled"),
+ func(p *runtime.KernelParamSpec) error {
+ if zswapCfg.ShrinkerEnabled() {
+ p.TypedSpec().Value = "Y"
+ } else {
+ p.TypedSpec().Value = "N"
+ }
+
+ return nil
+ }); err != nil {
+ return fmt.Errorf("error setting zswap config: %w", err)
+ }
+ }
+
+ if err = safe.CleanupOutputs[*runtime.KernelParamSpec](ctx, r); err != nil {
+ return fmt.Errorf("error cleaning up volume configuration: %w", err)
+ }
+ }
+}
diff --git a/internal/app/machined/pkg/controllers/block/zswap_status.go b/internal/app/machined/pkg/controllers/block/zswap_status.go
new file mode 100644
index 00000000000..aba1e37a6e2
--- /dev/null
+++ b/internal/app/machined/pkg/controllers/block/zswap_status.go
@@ -0,0 +1,131 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package block
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "time"
+
+ "github.com/cosi-project/runtime/pkg/controller"
+ "github.com/cosi-project/runtime/pkg/safe"
+ "github.com/dustin/go-humanize"
+ "go.uber.org/zap"
+
+ machineruntime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
+ "github.com/siderolabs/talos/pkg/machinery/resources/block"
+ "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
+)
+
+// ZswapStatusController provides a view of active swap devices.
+type ZswapStatusController struct {
+ V1Alpha1Mode machineruntime.Mode
+}
+
+// Name implements controller.Controller interface.
+func (ctrl *ZswapStatusController) Name() string {
+ return "block.ZswapStatusController"
+}
+
+// Inputs implements controller.Controller interface.
+func (ctrl *ZswapStatusController) Inputs() []controller.Input {
+ return []controller.Input{
+ {
+ // not really a dependency, but we refresh zswap status kernel param change
+ Namespace: runtime.NamespaceName,
+ Type: runtime.KernelParamStatusType,
+ Kind: controller.InputWeak,
+ },
+ }
+}
+
+// Outputs implements controller.Controller interface.
+func (ctrl *ZswapStatusController) Outputs() []controller.Output {
+ return []controller.Output{
+ {
+ Type: block.ZswapStatusType,
+ Kind: controller.OutputExclusive,
+ },
+ }
+}
+
+// Run implements controller.Controller interface.
+//
+//nolint:gocyclo
+func (ctrl *ZswapStatusController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
+ // in container mode, no zswap applies
+ if ctrl.V1Alpha1Mode == machineruntime.ModeContainer {
+ return nil
+ }
+
+ // there is no way to watch for zswap status, so we are going to poll every minute
+ ticker := time.NewTicker(time.Minute)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return nil
+ case <-r.EventCh():
+ case <-ticker.C:
+ }
+
+ r.StartTrackingOutputs()
+
+ // try to read a single status file to see if zswap is enabled
+ if _, err := os.ReadFile("/sys/kernel/debug/zswap/pool_total_size"); err == nil {
+ if err = safe.WriterModify(ctx, r, block.NewZswapStatus(block.NamespaceName, block.ZswapStatusID),
+ func(zs *block.ZswapStatus) error {
+ for _, p := range []struct {
+ name string
+ out *uint64
+ }{
+ {"pool_total_size", &zs.TypedSpec().TotalSizeBytes},
+ {"stored_pages", &zs.TypedSpec().StoredPages},
+ {"pool_limit_hit", &zs.TypedSpec().PoolLimitHit},
+ {"reject_reclaim_fail", &zs.TypedSpec().RejectReclaimFail},
+ {"reject_alloc_fail", &zs.TypedSpec().RejectAllocFail},
+ {"reject_kmemcache_fail", &zs.TypedSpec().RejectKmemcacheFail},
+ {"reject_compress_fail", &zs.TypedSpec().RejectCompressFail},
+ {"reject_compress_poor", &zs.TypedSpec().RejectCompressPoor},
+ {"written_back_pages", &zs.TypedSpec().WrittenBackPages},
+ } {
+ if err := ctrl.readZswapParam(p.name, p.out); err != nil {
+ return err
+ }
+ }
+
+ zs.TypedSpec().TotalSizeHuman = humanize.IBytes(zs.TypedSpec().TotalSizeBytes)
+
+ return nil
+ },
+ ); err != nil {
+ return fmt.Errorf("failed to create zswap status: %w", err)
+ }
+ }
+
+ if err := safe.CleanupOutputs[*block.ZswapStatus](ctx, r); err != nil {
+ return fmt.Errorf("failed to cleanup outputs: %w", err)
+ }
+ }
+}
+
+func (ctrl *ZswapStatusController) readZswapParam(name string, out *uint64) error {
+ content, err := os.ReadFile(filepath.Join("/sys/kernel/debug/zswap", name))
+ if err != nil {
+ return fmt.Errorf("failed to read zswap parameter %q: %w", name, err)
+ }
+
+ *out, err = strconv.ParseUint(string(bytes.TrimSpace(content)), 10, 64)
+ if err != nil {
+ return fmt.Errorf("failed to parse zswap parameter %q: %w", name, err)
+ }
+
+ return nil
+}
diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go
index 9a600648d21..5fccf512305 100644
--- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go
+++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go
@@ -110,6 +110,10 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
},
&block.VolumeManagerController{},
+ &block.ZswapConfigController{},
+ &block.ZswapStatusController{
+ V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
+ },
&cluster.AffiliateMergeController{},
cluster.NewConfigController(),
&cluster.DiscoveryServiceController{},
diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go
index 25e97752e36..ae1bdfd003c 100644
--- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go
+++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go
@@ -112,6 +112,7 @@ func NewState() (*State, error) {
&block.VolumeMountRequest{},
&block.VolumeMountStatus{},
&block.VolumeStatus{},
+ &block.ZswapStatus{},
&cluster.Affiliate{},
&cluster.Config{},
&cluster.Identity{},
diff --git a/internal/app/machined/pkg/startup/cgroups.go b/internal/app/machined/pkg/startup/cgroups.go
index cce62e5ea16..57d067bf4de 100644
--- a/internal/app/machined/pkg/startup/cgroups.go
+++ b/internal/app/machined/pkg/startup/cgroups.go
@@ -152,9 +152,10 @@ func CreateSystemCgroups(ctx context.Context, log *zap.Logger, rt runtime.Runtim
name: constants.CgroupApid,
resources: &cgroup2.Resources{
Memory: &cgroup2.Memory{
- Min: pointer.To[int64](constants.CgroupApidReservedMemory),
- Low: pointer.To[int64](constants.CgroupApidReservedMemory * 2),
- Max: zeroIfRace(pointer.To[int64](constants.CgroupApidMaxMemory)),
+ Min: pointer.To[int64](constants.CgroupApidReservedMemory),
+ Low: pointer.To[int64](constants.CgroupApidReservedMemory * 2),
+ Max: zeroIfRace(pointer.To[int64](constants.CgroupApidMaxMemory)),
+ Swap: pointer.To[int64](0),
},
CPU: &cgroup2.CPU{
Weight: pointer.To[uint64](cgroup.MillicoresToCPUWeight(cgroup.MilliCores(constants.CgroupApidMillicores))),
@@ -165,9 +166,10 @@ func CreateSystemCgroups(ctx context.Context, log *zap.Logger, rt runtime.Runtim
name: constants.CgroupTrustd,
resources: &cgroup2.Resources{
Memory: &cgroup2.Memory{
- Min: pointer.To[int64](constants.CgroupTrustdReservedMemory),
- Low: pointer.To[int64](constants.CgroupTrustdReservedMemory * 2),
- Max: zeroIfRace(pointer.To[int64](constants.CgroupTrustdMaxMemory)),
+ Min: pointer.To[int64](constants.CgroupTrustdReservedMemory),
+ Low: pointer.To[int64](constants.CgroupTrustdReservedMemory * 2),
+ Max: zeroIfRace(pointer.To[int64](constants.CgroupTrustdMaxMemory)),
+ Swap: pointer.To[int64](0),
},
CPU: &cgroup2.CPU{
Weight: pointer.To[uint64](cgroup.MillicoresToCPUWeight(cgroup.MilliCores(constants.CgroupTrustdMillicores))),
diff --git a/internal/integration/api/volumes.go b/internal/integration/api/volumes.go
index 3f42d73a64a..cf35a15dbfb 100644
--- a/internal/integration/api/volumes.go
+++ b/internal/integration/api/volumes.go
@@ -755,6 +755,29 @@ func (suite *VolumesSuite) TestSwapOnOff() {
}))
}
+// TestZswapStatus verifies that all zswap-enabled machines have zswap running.
+func (suite *VolumesSuite) TestZswapStatus() {
+ for _, node := range suite.DiscoverNodeInternalIPs(suite.ctx) {
+ suite.Run(node, func() {
+ ctx := client.WithNode(suite.ctx, node)
+
+ cfg, err := suite.ReadConfigFromNode(ctx)
+ suite.Require().NoError(err)
+
+ if cfg.ZswapConfig() == nil {
+ suite.T().Skipf("skipping test, zswap is not enabled on node %s", node)
+ }
+
+ rtestutils.AssertResource(ctx, suite.T(), suite.Client.COSI,
+ block.ZswapStatusID,
+ func(vs *block.ZswapStatus, asrt *assert.Assertions) {
+ suite.T().Logf("zswap total size %s, stored pages %d", vs.TypedSpec().TotalSizeHuman, vs.TypedSpec().StoredPages)
+ },
+ )
+ })
+ }
+}
+
func init() {
allSuites = append(allSuites, new(VolumesSuite))
}
diff --git a/internal/pkg/cgroups/cgroups.go b/internal/pkg/cgroups/cgroups.go
index dee24ad235b..47d86f8c42f 100644
--- a/internal/pkg/cgroups/cgroups.go
+++ b/internal/pkg/cgroups/cgroups.go
@@ -134,6 +134,10 @@ type Node struct {
MemorySwapMax Value
MemorySwapPeak Value
+ MemoryZswapCurrent Value
+ MemoryZswapMax Value
+ MemoryZswapWriteback Value
+
PidsCurrent Value
PidsEvents FlatMap
PidsMax Value
@@ -280,6 +284,12 @@ func (n *Node) Parse(filename string, r io.Reader) error {
return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapMax, r)
case "memory.swap.peak":
return parseSingleValue(ParseNewlineSeparatedValues, &n.MemorySwapPeak, r)
+ case "memory.zswap.current":
+ return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapCurrent, r)
+ case "memory.zswap.max":
+ return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapMax, r)
+ case "memory.zswap.writeback":
+ return parseSingleValue(ParseNewlineSeparatedValues, &n.MemoryZswapWriteback, r)
case "pids.current":
return parseSingleValue(ParseNewlineSeparatedValues, &n.PidsCurrent, r)
case "pids.events":
diff --git a/internal/pkg/mount/v2/pseudo.go b/internal/pkg/mount/v2/pseudo.go
index 0ecd6ced992..863ccf22f52 100644
--- a/internal/pkg/mount/v2/pseudo.go
+++ b/internal/pkg/mount/v2/pseudo.go
@@ -41,6 +41,7 @@ func PseudoSubMountPoints() Points {
NewPoint("securityfs", "/sys/kernel/security", "securityfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME)),
NewPoint("tracefs", "/sys/kernel/tracing", "tracefs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV)),
NewPoint("configfs", "/sys/kernel/config", "configfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME)),
+ NewPoint("debugfs", "/sys/kernel/debug", "debugfs", WithFlags(unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME)),
}
if _, err := os.Stat(constants.EFIVarsMountPoint); err == nil {
diff --git a/pkg/machinery/api/resource/definitions/block/block.pb.go b/pkg/machinery/api/resource/definitions/block/block.pb.go
index 4d1b02ac29d..5553c0fb6d5 100644
--- a/pkg/machinery/api/resource/definitions/block/block.pb.go
+++ b/pkg/machinery/api/resource/definitions/block/block.pb.go
@@ -2025,6 +2025,123 @@ func (x *VolumeStatusSpec) GetParentId() string {
return ""
}
+// ZswapStatusSpec is the spec for ZswapStatus resource.
+type ZswapStatusSpec struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TotalSizeBytes uint64 `protobuf:"varint,1,opt,name=total_size_bytes,json=totalSizeBytes,proto3" json:"total_size_bytes,omitempty"`
+ TotalSizeHuman string `protobuf:"bytes,2,opt,name=total_size_human,json=totalSizeHuman,proto3" json:"total_size_human,omitempty"`
+ StoredPages uint64 `protobuf:"varint,3,opt,name=stored_pages,json=storedPages,proto3" json:"stored_pages,omitempty"`
+ PoolLimitHit uint64 `protobuf:"varint,4,opt,name=pool_limit_hit,json=poolLimitHit,proto3" json:"pool_limit_hit,omitempty"`
+ RejectReclaimFail uint64 `protobuf:"varint,5,opt,name=reject_reclaim_fail,json=rejectReclaimFail,proto3" json:"reject_reclaim_fail,omitempty"`
+ RejectAllocFail uint64 `protobuf:"varint,6,opt,name=reject_alloc_fail,json=rejectAllocFail,proto3" json:"reject_alloc_fail,omitempty"`
+ RejectKmemcacheFail uint64 `protobuf:"varint,7,opt,name=reject_kmemcache_fail,json=rejectKmemcacheFail,proto3" json:"reject_kmemcache_fail,omitempty"`
+ RejectCompressFail uint64 `protobuf:"varint,8,opt,name=reject_compress_fail,json=rejectCompressFail,proto3" json:"reject_compress_fail,omitempty"`
+ RejectCompressPoor uint64 `protobuf:"varint,9,opt,name=reject_compress_poor,json=rejectCompressPoor,proto3" json:"reject_compress_poor,omitempty"`
+ WrittenBackPages uint64 `protobuf:"varint,10,opt,name=written_back_pages,json=writtenBackPages,proto3" json:"written_back_pages,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ZswapStatusSpec) Reset() {
+ *x = ZswapStatusSpec{}
+ mi := &file_resource_definitions_block_block_proto_msgTypes[24]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ZswapStatusSpec) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ZswapStatusSpec) ProtoMessage() {}
+
+func (x *ZswapStatusSpec) ProtoReflect() protoreflect.Message {
+ mi := &file_resource_definitions_block_block_proto_msgTypes[24]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ZswapStatusSpec.ProtoReflect.Descriptor instead.
+func (*ZswapStatusSpec) Descriptor() ([]byte, []int) {
+ return file_resource_definitions_block_block_proto_rawDescGZIP(), []int{24}
+}
+
+func (x *ZswapStatusSpec) GetTotalSizeBytes() uint64 {
+ if x != nil {
+ return x.TotalSizeBytes
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetTotalSizeHuman() string {
+ if x != nil {
+ return x.TotalSizeHuman
+ }
+ return ""
+}
+
+func (x *ZswapStatusSpec) GetStoredPages() uint64 {
+ if x != nil {
+ return x.StoredPages
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetPoolLimitHit() uint64 {
+ if x != nil {
+ return x.PoolLimitHit
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetRejectReclaimFail() uint64 {
+ if x != nil {
+ return x.RejectReclaimFail
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetRejectAllocFail() uint64 {
+ if x != nil {
+ return x.RejectAllocFail
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetRejectKmemcacheFail() uint64 {
+ if x != nil {
+ return x.RejectKmemcacheFail
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetRejectCompressFail() uint64 {
+ if x != nil {
+ return x.RejectCompressFail
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetRejectCompressPoor() uint64 {
+ if x != nil {
+ return x.RejectCompressPoor
+ }
+ return 0
+}
+
+func (x *ZswapStatusSpec) GetWrittenBackPages() uint64 {
+ if x != nil {
+ return x.WrittenBackPages
+ }
+ return 0
+}
+
var File_resource_definitions_block_block_proto protoreflect.FileDescriptor
const file_resource_definitions_block_block_proto_rawDesc = "" +
@@ -2225,7 +2342,19 @@ const file_resource_definitions_block_block_proto_rawDesc = "" +
"\x04type\x18\x10 \x01(\x0e21.talos.resource.definitions.enums.BlockVolumeTypeR\x04type\x12<\n" +
"\x1aconfigured_encryption_keys\x18\x11 \x03(\tR\x18configuredEncryptionKeys\x12\\\n" +
"\fsymlink_spec\x18\x12 \x01(\v29.talos.resource.definitions.block.SymlinkProvisioningSpecR\vsymlinkSpec\x12\x1b\n" +
- "\tparent_id\x18\x13 \x01(\tR\bparentIdBt\n" +
+ "\tparent_id\x18\x13 \x01(\tR\bparentId\"\xd0\x03\n" +
+ "\x0fZswapStatusSpec\x12(\n" +
+ "\x10total_size_bytes\x18\x01 \x01(\x04R\x0etotalSizeBytes\x12(\n" +
+ "\x10total_size_human\x18\x02 \x01(\tR\x0etotalSizeHuman\x12!\n" +
+ "\fstored_pages\x18\x03 \x01(\x04R\vstoredPages\x12$\n" +
+ "\x0epool_limit_hit\x18\x04 \x01(\x04R\fpoolLimitHit\x12.\n" +
+ "\x13reject_reclaim_fail\x18\x05 \x01(\x04R\x11rejectReclaimFail\x12*\n" +
+ "\x11reject_alloc_fail\x18\x06 \x01(\x04R\x0frejectAllocFail\x122\n" +
+ "\x15reject_kmemcache_fail\x18\a \x01(\x04R\x13rejectKmemcacheFail\x120\n" +
+ "\x14reject_compress_fail\x18\b \x01(\x04R\x12rejectCompressFail\x120\n" +
+ "\x14reject_compress_poor\x18\t \x01(\x04R\x12rejectCompressPoor\x12,\n" +
+ "\x12written_back_pages\x18\n" +
+ " \x01(\x04R\x10writtenBackPagesBt\n" +
"(dev.talos.api.resource.definitions.blockZHgithub.com/siderolabs/talos/pkg/machinery/api/resource/definitions/blockb\x06proto3"
var (
@@ -2240,7 +2369,7 @@ func file_resource_definitions_block_block_proto_rawDescGZIP() []byte {
return file_resource_definitions_block_block_proto_rawDescData
}
-var file_resource_definitions_block_block_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
+var file_resource_definitions_block_block_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
var file_resource_definitions_block_block_proto_goTypes = []any{
(*DeviceSpec)(nil), // 0: talos.resource.definitions.block.DeviceSpec
(*DiscoveredVolumeSpec)(nil), // 1: talos.resource.definitions.block.DiscoveredVolumeSpec
@@ -2266,38 +2395,39 @@ var file_resource_definitions_block_block_proto_goTypes = []any{
(*VolumeMountRequestSpec)(nil), // 21: talos.resource.definitions.block.VolumeMountRequestSpec
(*VolumeMountStatusSpec)(nil), // 22: talos.resource.definitions.block.VolumeMountStatusSpec
(*VolumeStatusSpec)(nil), // 23: talos.resource.definitions.block.VolumeStatusSpec
- (*v1alpha1.CheckedExpr)(nil), // 24: google.api.expr.v1alpha1.CheckedExpr
- (enums.BlockEncryptionKeyType)(0), // 25: talos.resource.definitions.enums.BlockEncryptionKeyType
- (enums.BlockEncryptionProviderType)(0), // 26: talos.resource.definitions.enums.BlockEncryptionProviderType
- (enums.BlockFilesystemType)(0), // 27: talos.resource.definitions.enums.BlockFilesystemType
- (enums.BlockVolumeType)(0), // 28: talos.resource.definitions.enums.BlockVolumeType
- (enums.BlockVolumePhase)(0), // 29: talos.resource.definitions.enums.BlockVolumePhase
+ (*ZswapStatusSpec)(nil), // 24: talos.resource.definitions.block.ZswapStatusSpec
+ (*v1alpha1.CheckedExpr)(nil), // 25: google.api.expr.v1alpha1.CheckedExpr
+ (enums.BlockEncryptionKeyType)(0), // 26: talos.resource.definitions.enums.BlockEncryptionKeyType
+ (enums.BlockEncryptionProviderType)(0), // 27: talos.resource.definitions.enums.BlockEncryptionProviderType
+ (enums.BlockFilesystemType)(0), // 28: talos.resource.definitions.enums.BlockFilesystemType
+ (enums.BlockVolumeType)(0), // 29: talos.resource.definitions.enums.BlockVolumeType
+ (enums.BlockVolumePhase)(0), // 30: talos.resource.definitions.enums.BlockVolumePhase
}
var file_resource_definitions_block_block_proto_depIdxs = []int32{
- 24, // 0: talos.resource.definitions.block.DiskSelector.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
- 25, // 1: talos.resource.definitions.block.EncryptionKey.type:type_name -> talos.resource.definitions.enums.BlockEncryptionKeyType
- 26, // 2: talos.resource.definitions.block.EncryptionSpec.provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
+ 25, // 0: talos.resource.definitions.block.DiskSelector.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
+ 26, // 1: talos.resource.definitions.block.EncryptionKey.type:type_name -> talos.resource.definitions.enums.BlockEncryptionKeyType
+ 27, // 2: talos.resource.definitions.block.EncryptionSpec.provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
6, // 3: talos.resource.definitions.block.EncryptionSpec.keys:type_name -> talos.resource.definitions.block.EncryptionKey
- 27, // 4: talos.resource.definitions.block.FilesystemSpec.type:type_name -> talos.resource.definitions.enums.BlockFilesystemType
- 24, // 5: talos.resource.definitions.block.LocatorSpec.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
+ 28, // 4: talos.resource.definitions.block.FilesystemSpec.type:type_name -> talos.resource.definitions.enums.BlockFilesystemType
+ 25, // 5: talos.resource.definitions.block.LocatorSpec.match:type_name -> google.api.expr.v1alpha1.CheckedExpr
10, // 6: talos.resource.definitions.block.MountStatusSpec.spec:type_name -> talos.resource.definitions.block.MountRequestSpec
- 27, // 7: talos.resource.definitions.block.MountStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
- 26, // 8: talos.resource.definitions.block.MountStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
+ 28, // 7: talos.resource.definitions.block.MountStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
+ 27, // 8: talos.resource.definitions.block.MountStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
4, // 9: talos.resource.definitions.block.ProvisioningSpec.disk_selector:type_name -> talos.resource.definitions.block.DiskSelector
13, // 10: talos.resource.definitions.block.ProvisioningSpec.partition_spec:type_name -> talos.resource.definitions.block.PartitionSpec
8, // 11: talos.resource.definitions.block.ProvisioningSpec.filesystem_spec:type_name -> talos.resource.definitions.block.FilesystemSpec
- 28, // 12: talos.resource.definitions.block.VolumeConfigSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
+ 29, // 12: talos.resource.definitions.block.VolumeConfigSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
14, // 13: talos.resource.definitions.block.VolumeConfigSpec.provisioning:type_name -> talos.resource.definitions.block.ProvisioningSpec
9, // 14: talos.resource.definitions.block.VolumeConfigSpec.locator:type_name -> talos.resource.definitions.block.LocatorSpec
11, // 15: talos.resource.definitions.block.VolumeConfigSpec.mount:type_name -> talos.resource.definitions.block.MountSpec
7, // 16: talos.resource.definitions.block.VolumeConfigSpec.encryption:type_name -> talos.resource.definitions.block.EncryptionSpec
16, // 17: talos.resource.definitions.block.VolumeConfigSpec.symlink:type_name -> talos.resource.definitions.block.SymlinkProvisioningSpec
- 29, // 18: talos.resource.definitions.block.VolumeStatusSpec.phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
- 29, // 19: talos.resource.definitions.block.VolumeStatusSpec.pre_fail_phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
- 27, // 20: talos.resource.definitions.block.VolumeStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
- 26, // 21: talos.resource.definitions.block.VolumeStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
+ 30, // 18: talos.resource.definitions.block.VolumeStatusSpec.phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
+ 30, // 19: talos.resource.definitions.block.VolumeStatusSpec.pre_fail_phase:type_name -> talos.resource.definitions.enums.BlockVolumePhase
+ 28, // 20: talos.resource.definitions.block.VolumeStatusSpec.filesystem:type_name -> talos.resource.definitions.enums.BlockFilesystemType
+ 27, // 21: talos.resource.definitions.block.VolumeStatusSpec.encryption_provider:type_name -> talos.resource.definitions.enums.BlockEncryptionProviderType
11, // 22: talos.resource.definitions.block.VolumeStatusSpec.mount_spec:type_name -> talos.resource.definitions.block.MountSpec
- 28, // 23: talos.resource.definitions.block.VolumeStatusSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
+ 29, // 23: talos.resource.definitions.block.VolumeStatusSpec.type:type_name -> talos.resource.definitions.enums.BlockVolumeType
16, // 24: talos.resource.definitions.block.VolumeStatusSpec.symlink_spec:type_name -> talos.resource.definitions.block.SymlinkProvisioningSpec
25, // [25:25] is the sub-list for method output_type
25, // [25:25] is the sub-list for method input_type
@@ -2317,7 +2447,7 @@ func file_resource_definitions_block_block_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_resource_definitions_block_block_proto_rawDesc), len(file_resource_definitions_block_block_proto_rawDesc)),
NumEnums: 0,
- NumMessages: 24,
+ NumMessages: 25,
NumExtensions: 0,
NumServices: 0,
},
diff --git a/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go b/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go
index ad2c385869c..396d32715e6 100644
--- a/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go
+++ b/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go
@@ -1859,6 +1859,91 @@ func (m *VolumeStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
+func (m *ZswapStatusSpec) MarshalVT() (dAtA []byte, err error) {
+ if m == nil {
+ return nil, nil
+ }
+ size := m.SizeVT()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBufferVT(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *ZswapStatusSpec) MarshalToVT(dAtA []byte) (int, error) {
+ size := m.SizeVT()
+ return m.MarshalToSizedBufferVT(dAtA[:size])
+}
+
+func (m *ZswapStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
+ if m == nil {
+ return 0, nil
+ }
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ if m.unknownFields != nil {
+ i -= len(m.unknownFields)
+ copy(dAtA[i:], m.unknownFields)
+ }
+ if m.WrittenBackPages != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.WrittenBackPages))
+ i--
+ dAtA[i] = 0x50
+ }
+ if m.RejectCompressPoor != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectCompressPoor))
+ i--
+ dAtA[i] = 0x48
+ }
+ if m.RejectCompressFail != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectCompressFail))
+ i--
+ dAtA[i] = 0x40
+ }
+ if m.RejectKmemcacheFail != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectKmemcacheFail))
+ i--
+ dAtA[i] = 0x38
+ }
+ if m.RejectAllocFail != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectAllocFail))
+ i--
+ dAtA[i] = 0x30
+ }
+ if m.RejectReclaimFail != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.RejectReclaimFail))
+ i--
+ dAtA[i] = 0x28
+ }
+ if m.PoolLimitHit != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.PoolLimitHit))
+ i--
+ dAtA[i] = 0x20
+ }
+ if m.StoredPages != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.StoredPages))
+ i--
+ dAtA[i] = 0x18
+ }
+ if len(m.TotalSizeHuman) > 0 {
+ i -= len(m.TotalSizeHuman)
+ copy(dAtA[i:], m.TotalSizeHuman)
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.TotalSizeHuman)))
+ i--
+ dAtA[i] = 0x12
+ }
+ if m.TotalSizeBytes != 0 {
+ i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TotalSizeBytes))
+ i--
+ dAtA[i] = 0x8
+ }
+ return len(dAtA) - i, nil
+}
+
func (m *DeviceSpec) SizeVT() (n int) {
if m == nil {
return 0
@@ -2631,6 +2716,47 @@ func (m *VolumeStatusSpec) SizeVT() (n int) {
return n
}
+func (m *ZswapStatusSpec) SizeVT() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ if m.TotalSizeBytes != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.TotalSizeBytes))
+ }
+ l = len(m.TotalSizeHuman)
+ if l > 0 {
+ n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
+ }
+ if m.StoredPages != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.StoredPages))
+ }
+ if m.PoolLimitHit != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.PoolLimitHit))
+ }
+ if m.RejectReclaimFail != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectReclaimFail))
+ }
+ if m.RejectAllocFail != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectAllocFail))
+ }
+ if m.RejectKmemcacheFail != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectKmemcacheFail))
+ }
+ if m.RejectCompressFail != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectCompressFail))
+ }
+ if m.RejectCompressPoor != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.RejectCompressPoor))
+ }
+ if m.WrittenBackPages != 0 {
+ n += 1 + protohelpers.SizeOfVarint(uint64(m.WrittenBackPages))
+ }
+ n += len(m.unknownFields)
+ return n
+}
+
func (m *DeviceSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
@@ -7662,3 +7788,257 @@ func (m *VolumeStatusSpec) UnmarshalVT(dAtA []byte) error {
}
return nil
}
+func (m *ZswapStatusSpec) UnmarshalVT(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: ZswapStatusSpec: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: ZswapStatusSpec: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field TotalSizeBytes", wireType)
+ }
+ m.TotalSizeBytes = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.TotalSizeBytes |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field TotalSizeHuman", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return protohelpers.ErrInvalidLength
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex < 0 {
+ return protohelpers.ErrInvalidLength
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.TotalSizeHuman = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 3:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field StoredPages", wireType)
+ }
+ m.StoredPages = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.StoredPages |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 4:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field PoolLimitHit", wireType)
+ }
+ m.PoolLimitHit = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.PoolLimitHit |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 5:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field RejectReclaimFail", wireType)
+ }
+ m.RejectReclaimFail = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.RejectReclaimFail |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 6:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field RejectAllocFail", wireType)
+ }
+ m.RejectAllocFail = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.RejectAllocFail |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 7:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field RejectKmemcacheFail", wireType)
+ }
+ m.RejectKmemcacheFail = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.RejectKmemcacheFail |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 8:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field RejectCompressFail", wireType)
+ }
+ m.RejectCompressFail = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.RejectCompressFail |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 9:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field RejectCompressPoor", wireType)
+ }
+ m.RejectCompressPoor = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.RejectCompressPoor |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 10:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field WrittenBackPages", wireType)
+ }
+ m.WrittenBackPages = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return protohelpers.ErrIntOverflow
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.WrittenBackPages |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ default:
+ iNdEx = preIndex
+ skippy, err := protohelpers.Skip(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return protohelpers.ErrInvalidLength
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
diff --git a/pkg/machinery/config/config/config.go b/pkg/machinery/config/config/config.go
index 7303cc54369..f616668a193 100644
--- a/pkg/machinery/config/config/config.go
+++ b/pkg/machinery/config/config/config.go
@@ -21,4 +21,5 @@ type Config interface { //nolint:interfacebloat
EthernetConfigs() []EthernetConfig
UserVolumeConfigs() []UserVolumeConfig
SwapVolumeConfigs() []SwapVolumeConfig
+ ZswapConfig() ZswapConfig
}
diff --git a/pkg/machinery/config/config/volume.go b/pkg/machinery/config/config/volume.go
index 6ae6dab8dff..e222bc06eb9 100644
--- a/pkg/machinery/config/config/volume.go
+++ b/pkg/machinery/config/config/volume.go
@@ -98,3 +98,10 @@ type SwapVolumeConfig interface {
Provisioning() VolumeProvisioningConfig
Encryption() EncryptionConfig
}
+
+// ZswapConfig defines the interface to access zswap configuration.
+type ZswapConfig interface {
+ ZswapConfigSignal()
+ MaxPoolPercent() int
+ ShrinkerEnabled() bool
+}
diff --git a/pkg/machinery/config/container/container.go b/pkg/machinery/config/container/container.go
index 802d2fd6eda..ab80cbe80bf 100644
--- a/pkg/machinery/config/container/container.go
+++ b/pkg/machinery/config/container/container.go
@@ -230,6 +230,16 @@ func (container *Container) SwapVolumeConfigs() []config.SwapVolumeConfig {
return findMatchingDocs[config.SwapVolumeConfig](container.documents)
}
+// ZswapConfig implements config.Config interface.
+func (container *Container) ZswapConfig() config.ZswapConfig {
+ matching := findMatchingDocs[config.ZswapConfig](container.documents)
+ if len(matching) == 0 {
+ return nil
+ }
+
+ return matching[0]
+}
+
// Bytes returns source YAML representation (if available) or does default encoding.
func (container *Container) Bytes() ([]byte, error) {
if !container.readonly {
diff --git a/pkg/machinery/config/schemas/config.schema.json b/pkg/machinery/config/schemas/config.schema.json
index aa1de056032..3d259d8d792 100644
--- a/pkg/machinery/config/schemas/config.schema.json
+++ b/pkg/machinery/config/schemas/config.schema.json
@@ -370,6 +370,49 @@
],
"description": "VolumeConfig is a system volume configuration document.\\nNote: at the moment, only `EPHEMERAL` and `IMAGE-CACHE` system volumes are supported.\\n"
},
+ "block.ZswapConfigV1Alpha1": {
+ "properties": {
+ "apiVersion": {
+ "enum": [
+ "v1alpha1"
+ ],
+ "title": "apiVersion",
+ "description": "apiVersion is the API version of the resource.\n",
+ "markdownDescription": "apiVersion is the API version of the resource.",
+ "x-intellij-html-description": "\u003cp\u003eapiVersion is the API version of the resource.\u003c/p\u003e\n"
+ },
+ "kind": {
+ "enum": [
+ "ZswapConfig"
+ ],
+ "title": "kind",
+ "description": "kind is the kind of the resource.\n",
+ "markdownDescription": "kind is the kind of the resource.",
+ "x-intellij-html-description": "\u003cp\u003ekind is the kind of the resource.\u003c/p\u003e\n"
+ },
+ "maxPoolPercent": {
+ "type": "integer",
+ "title": "maxPoolPercent",
+ "description": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\n",
+ "markdownDescription": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.",
+ "x-intellij-html-description": "\u003cp\u003eThe maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\u003c/p\u003e\n"
+ },
+ "shrinkerEnabled": {
+ "type": "boolean",
+ "title": "shrinkerEnabled",
+ "description": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\n",
+ "markdownDescription": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.",
+ "x-intellij-html-description": "\u003cp\u003eEnable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\u003c/p\u003e\n"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "apiVersion",
+ "kind"
+ ],
+ "description": "ZswapConfig is a zswap (compressed memory) configuration document.\\nWhen zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.\\nThe compressed pages are stored in a memory pool, which is used to avoid writing to disk\\nwhen the system is under memory pressure.\\n"
+ },
"extensions.ConfigFile": {
"properties": {
"content": {
@@ -4242,6 +4285,9 @@
{
"$ref": "#/$defs/block.VolumeConfigV1Alpha1"
},
+ {
+ "$ref": "#/$defs/block.ZswapConfigV1Alpha1"
+ },
{
"$ref": "#/$defs/extensions.ServiceConfigV1Alpha1"
},
diff --git a/pkg/machinery/config/types/block/block.go b/pkg/machinery/config/types/block/block.go
index f054ae4b06c..ce95750847d 100644
--- a/pkg/machinery/config/types/block/block.go
+++ b/pkg/machinery/config/types/block/block.go
@@ -5,6 +5,6 @@
// Package block provides block device and volume configuration documents.
package block
-//go:generate docgen -output block_doc.go block.go encryption.go swap_volume_config.go user_volume_config.go volume_config.go
+//go:generate docgen -output block_doc.go block.go encryption.go swap_volume_config.go user_volume_config.go volume_config.go zswap_config.go
-//go:generate deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go .
+//go:generate deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -type ZswapConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go .
diff --git a/pkg/machinery/config/types/block/block_doc.go b/pkg/machinery/config/types/block/block_doc.go
index 417ed257790..e1055d2326a 100644
--- a/pkg/machinery/config/types/block/block_doc.go
+++ b/pkg/machinery/config/types/block/block_doc.go
@@ -462,6 +462,35 @@ func (DiskSelector) Doc() *encoder.Doc {
return doc
}
+func (ZswapConfigV1Alpha1) Doc() *encoder.Doc {
+ doc := &encoder.Doc{
+ Type: "ZswapConfig",
+ Comments: [3]string{"" /* encoder.HeadComment */, "ZswapConfig is a zswap (compressed memory) configuration document." /* encoder.LineComment */, "" /* encoder.FootComment */},
+ Description: "ZswapConfig is a zswap (compressed memory) configuration document.\nWhen zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.\nThe compressed pages are stored in a memory pool, which is used to avoid writing to disk\nwhen the system is under memory pressure.\n",
+ Fields: []encoder.Doc{
+ {},
+ {
+ Name: "maxPoolPercent",
+ Type: "int",
+ Note: "",
+ Description: "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.",
+ Comments: [3]string{"" /* encoder.HeadComment */, "The maximum percent of memory that zswap can use." /* encoder.LineComment */, "" /* encoder.FootComment */},
+ },
+ {
+ Name: "shrinkerEnabled",
+ Type: "bool",
+ Note: "",
+ Description: "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.",
+ Comments: [3]string{"" /* encoder.HeadComment */, "Enable the shrinker feature: kernel might move" /* encoder.LineComment */, "" /* encoder.FootComment */},
+ },
+ },
+ }
+
+ doc.AddExample("", exampleZswapConfigV1Alpha1())
+
+ return doc
+}
+
// GetFileDoc returns documentation for the file block_doc.go.
func GetFileDoc() *encoder.FileDoc {
return &encoder.FileDoc{
@@ -480,6 +509,7 @@ func GetFileDoc() *encoder.FileDoc {
VolumeConfigV1Alpha1{}.Doc(),
ProvisioningSpec{}.Doc(),
DiskSelector{}.Doc(),
+ ZswapConfigV1Alpha1{}.Doc(),
},
}
}
diff --git a/pkg/machinery/config/types/block/deep_copy.generated.go b/pkg/machinery/config/types/block/deep_copy.generated.go
index 4710a21f0b8..47ad4e53e8c 100644
--- a/pkg/machinery/config/types/block/deep_copy.generated.go
+++ b/pkg/machinery/config/types/block/deep_copy.generated.go
@@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-// Code generated by "deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
+// Code generated by "deep-copy -type SwapVolumeConfigV1Alpha1 -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -type ZswapConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
package block
@@ -143,3 +143,17 @@ func (o *VolumeConfigV1Alpha1) DeepCopy() *VolumeConfigV1Alpha1 {
}
return &cp
}
+
+// DeepCopy generates a deep copy of *ZswapConfigV1Alpha1.
+func (o *ZswapConfigV1Alpha1) DeepCopy() *ZswapConfigV1Alpha1 {
+ var cp ZswapConfigV1Alpha1 = *o
+ if o.MaxPoolPercentConfig != nil {
+ cp.MaxPoolPercentConfig = new(int)
+ *cp.MaxPoolPercentConfig = *o.MaxPoolPercentConfig
+ }
+ if o.ShrinkerEnabledConfig != nil {
+ cp.ShrinkerEnabledConfig = new(bool)
+ *cp.ShrinkerEnabledConfig = *o.ShrinkerEnabledConfig
+ }
+ return &cp
+}
diff --git a/pkg/machinery/config/types/block/testdata/zswapconfig_full.yaml b/pkg/machinery/config/types/block/testdata/zswapconfig_full.yaml
new file mode 100644
index 00000000000..156bc23ed47
--- /dev/null
+++ b/pkg/machinery/config/types/block/testdata/zswapconfig_full.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1alpha1
+kind: ZswapConfig
+maxPoolPercent: 50
+shrinkerEnabled: true
diff --git a/pkg/machinery/config/types/block/testdata/zswapconfig_min.yaml b/pkg/machinery/config/types/block/testdata/zswapconfig_min.yaml
new file mode 100644
index 00000000000..b3c94e47eea
--- /dev/null
+++ b/pkg/machinery/config/types/block/testdata/zswapconfig_min.yaml
@@ -0,0 +1,2 @@
+apiVersion: v1alpha1
+kind: ZswapConfig
diff --git a/pkg/machinery/config/types/block/zswap_config.go b/pkg/machinery/config/types/block/zswap_config.go
new file mode 100644
index 00000000000..4766166dbe5
--- /dev/null
+++ b/pkg/machinery/config/types/block/zswap_config.go
@@ -0,0 +1,126 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package block
+
+//docgen:jsonschema
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/siderolabs/go-pointer"
+
+ "github.com/siderolabs/talos/pkg/machinery/config/config"
+ "github.com/siderolabs/talos/pkg/machinery/config/internal/registry"
+ "github.com/siderolabs/talos/pkg/machinery/config/types/meta"
+ "github.com/siderolabs/talos/pkg/machinery/config/validation"
+)
+
+// ZswapConfigKind is a config document kind.
+const ZswapConfigKind = "ZswapConfig"
+
+func init() {
+ registry.Register(ZswapConfigKind, func(version string) config.Document {
+ switch version {
+ case "v1alpha1": //nolint:goconst
+ return &ZswapConfigV1Alpha1{}
+ default:
+ return nil
+ }
+ })
+}
+
+// Check interfaces.
+var (
+ _ config.ZswapConfig = &ZswapConfigV1Alpha1{}
+ _ config.Validator = &ZswapConfigV1Alpha1{}
+)
+
+// ZswapConfigV1Alpha1 is a zswap (compressed memory) configuration document.
+//
+// description: |
+// When zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.
+// The compressed pages are stored in a memory pool, which is used to avoid writing to disk
+// when the system is under memory pressure.
+// examples:
+// - value: exampleZswapConfigV1Alpha1()
+// alias: ZswapConfig
+// schemaRoot: true
+// schemaMeta: v1alpha1/ZswapConfig
+type ZswapConfigV1Alpha1 struct {
+ meta.Meta `yaml:",inline"`
+ // description: |
+ // The maximum percent of memory that zswap can use.
+ // This is a percentage of the total system memory.
+ // The value must be between 0 and 100.
+ MaxPoolPercentConfig *int `yaml:"maxPoolPercent,omitempty"`
+ // description: |
+ // Enable the shrinker feature: kernel might move
+ // cold pages from zswap to swap device to free up memory
+ // for other use cases.
+ ShrinkerEnabledConfig *bool `yaml:"shrinkerEnabled,omitempty"`
+}
+
+// NewZswapConfigV1Alpha1 creates a new zswap config document.
+func NewZswapConfigV1Alpha1() *ZswapConfigV1Alpha1 {
+ return &ZswapConfigV1Alpha1{
+ Meta: meta.Meta{
+ MetaKind: ZswapConfigKind,
+ MetaAPIVersion: "v1alpha1",
+ },
+ }
+}
+
+func exampleZswapConfigV1Alpha1() *ZswapConfigV1Alpha1 {
+ cfg := NewZswapConfigV1Alpha1()
+ cfg.MaxPoolPercentConfig = pointer.To(25)
+ cfg.ShrinkerEnabledConfig = pointer.To(true)
+
+ return cfg
+}
+
+// Clone implements config.Document interface.
+func (s *ZswapConfigV1Alpha1) Clone() config.Document {
+ return s.DeepCopy()
+}
+
+// Validate implements config.Validator interface.
+//
+//nolint:gocyclo
+func (s *ZswapConfigV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) {
+ var (
+ warnings []string
+ validationErrors error
+ )
+
+ if s.MaxPoolPercentConfig != nil {
+ if *s.MaxPoolPercentConfig < 0 || *s.MaxPoolPercentConfig > 100 {
+ validationErrors = errors.Join(validationErrors, fmt.Errorf("maxPoolPercent must be between 0 and 100"))
+ }
+ }
+
+ return warnings, validationErrors
+}
+
+// ZswapConfigSignal is a signal for zswap config.
+func (s *ZswapConfigV1Alpha1) ZswapConfigSignal() {}
+
+// MaxPoolPercent implements config.ZswapConfig interface.
+func (s *ZswapConfigV1Alpha1) MaxPoolPercent() int {
+ if s.MaxPoolPercentConfig == nil {
+ return 20
+ }
+
+ return pointer.SafeDeref(s.MaxPoolPercentConfig)
+}
+
+// ShrinkerEnabled implements config.ZswapConfig interface.
+func (s *ZswapConfigV1Alpha1) ShrinkerEnabled() bool {
+ if s.ShrinkerEnabledConfig == nil {
+ return false
+ }
+
+ return pointer.SafeDeref(s.ShrinkerEnabledConfig)
+}
diff --git a/pkg/machinery/config/types/block/zswap_config_test.go b/pkg/machinery/config/types/block/zswap_config_test.go
new file mode 100644
index 00000000000..93cfde20d33
--- /dev/null
+++ b/pkg/machinery/config/types/block/zswap_config_test.go
@@ -0,0 +1,141 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+//nolint:dupl,goconst
+package block_test
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/siderolabs/go-pointer"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/siderolabs/talos/pkg/machinery/config/configloader"
+ "github.com/siderolabs/talos/pkg/machinery/config/encoder"
+ "github.com/siderolabs/talos/pkg/machinery/config/types/block"
+)
+
+func TestZswapConfigMarshalUnmarshal(t *testing.T) {
+ t.Parallel()
+
+ for _, test := range []struct {
+ name string
+
+ filename string
+ cfg func(t *testing.T) *block.ZswapConfigV1Alpha1
+ }{
+ {
+ name: "full config",
+ filename: "zswapconfig_full.yaml",
+ cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
+ c := block.NewZswapConfigV1Alpha1()
+ c.MaxPoolPercentConfig = pointer.To(50)
+ c.ShrinkerEnabledConfig = pointer.To(true)
+
+ return c
+ },
+ },
+ {
+ name: "min config",
+ filename: "zswapconfig_min.yaml",
+ cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
+ c := block.NewZswapConfigV1Alpha1()
+
+ return c
+ },
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ t.Parallel()
+
+ cfg := test.cfg(t)
+
+ warnings, err := cfg.Validate(validationMode{})
+ require.NoError(t, err)
+ require.Empty(t, warnings)
+
+ marshaled, err := encoder.NewEncoder(cfg, encoder.WithComments(encoder.CommentsDisabled)).Encode()
+ require.NoError(t, err)
+
+ t.Log(string(marshaled))
+
+ expectedMarshaled, err := os.ReadFile(filepath.Join("testdata", test.filename))
+ require.NoError(t, err)
+
+ assert.Equal(t, string(expectedMarshaled), string(marshaled))
+
+ provider, err := configloader.NewFromBytes(expectedMarshaled)
+ require.NoError(t, err)
+
+ docs := provider.Documents()
+ require.Len(t, docs, 1)
+
+ assert.Equal(t, cfg, docs[0])
+ })
+ }
+}
+
+func TestZswapVolumeConfigValidate(t *testing.T) {
+ t.Parallel()
+
+ for _, test := range []struct {
+ name string
+
+ cfg func(t *testing.T) *block.ZswapConfigV1Alpha1
+
+ expectedErrors string
+ }{
+ {
+ name: "minimal",
+
+ cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
+ c := block.NewZswapConfigV1Alpha1()
+
+ return c
+ },
+ },
+ {
+ name: "full",
+
+ cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
+ c := block.NewZswapConfigV1Alpha1()
+ c.MaxPoolPercentConfig = pointer.To(50)
+ c.ShrinkerEnabledConfig = pointer.To(true)
+
+ return c
+ },
+ },
+ {
+ name: "invalid percent",
+
+ cfg: func(t *testing.T) *block.ZswapConfigV1Alpha1 {
+ c := block.NewZswapConfigV1Alpha1()
+ c.MaxPoolPercentConfig = pointer.To(150)
+
+ return c
+ },
+
+ expectedErrors: "maxPoolPercent must be between 0 and 100",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ t.Parallel()
+
+ cfg := test.cfg(t)
+
+ _, err := cfg.Validate(validationMode{})
+
+ if test.expectedErrors == "" {
+ require.NoError(t, err)
+ } else {
+ require.Error(t, err)
+
+ assert.EqualError(t, err, test.expectedErrors)
+ }
+ })
+ }
+}
diff --git a/pkg/machinery/resources/block/block.go b/pkg/machinery/resources/block/block.go
index de6fa1cf025..bb235ffd5a6 100644
--- a/pkg/machinery/resources/block/block.go
+++ b/pkg/machinery/resources/block/block.go
@@ -19,7 +19,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/resources/v1alpha1"
)
-//go:generate deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
+//go:generate deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -type ZswapStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
//go:generate enumer -type=VolumeType,VolumePhase,FilesystemType,EncryptionKeyType,EncryptionProviderType -linecomment -text
diff --git a/pkg/machinery/resources/block/block_test.go b/pkg/machinery/resources/block/block_test.go
index 22312ff71bb..9e4ffcb5a5a 100644
--- a/pkg/machinery/resources/block/block_test.go
+++ b/pkg/machinery/resources/block/block_test.go
@@ -40,6 +40,7 @@ func TestRegisterResource(t *testing.T) {
&block.VolumeMountRequest{},
&block.VolumeMountStatus{},
&block.VolumeStatus{},
+ &block.ZswapStatus{},
} {
assert.NoError(t, resourceRegistry.Register(ctx, resource))
}
diff --git a/pkg/machinery/resources/block/deep_copy.generated.go b/pkg/machinery/resources/block/deep_copy.generated.go
index f8835e785a5..47e458ae910 100644
--- a/pkg/machinery/resources/block/deep_copy.generated.go
+++ b/pkg/machinery/resources/block/deep_copy.generated.go
@@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-// Code generated by "deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
+// Code generated by "deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiscoveryRefreshRequestSpec -type DiscoveryRefreshStatusSpec -type DiskSpec -type MountRequestSpec -type MountStatusSpec -type SwapStatusSpec -type SymlinkSpec -type SystemDiskSpec -type UserDiskConfigStatusSpec -type VolumeConfigSpec -type VolumeLifecycleSpec -type VolumeMountRequestSpec -type VolumeMountStatusSpec -type VolumeStatusSpec -type ZswapStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
package block
@@ -148,3 +148,9 @@ func (o VolumeStatusSpec) DeepCopy() VolumeStatusSpec {
}
return cp
}
+
+// DeepCopy generates a deep copy of ZswapStatusSpec.
+func (o ZswapStatusSpec) DeepCopy() ZswapStatusSpec {
+ var cp ZswapStatusSpec = o
+ return cp
+}
diff --git a/pkg/machinery/resources/block/zswap_status.go b/pkg/machinery/resources/block/zswap_status.go
new file mode 100644
index 00000000000..8e7d76ed5b9
--- /dev/null
+++ b/pkg/machinery/resources/block/zswap_status.go
@@ -0,0 +1,86 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package block
+
+import (
+ "github.com/cosi-project/runtime/pkg/resource"
+ "github.com/cosi-project/runtime/pkg/resource/meta"
+ "github.com/cosi-project/runtime/pkg/resource/protobuf"
+ "github.com/cosi-project/runtime/pkg/resource/typed"
+
+ "github.com/siderolabs/talos/pkg/machinery/proto"
+)
+
+// ZswapStatusType is type of ZswapStatus resource.
+const ZswapStatusType = resource.Type("ZswapStatuses.block.talos.dev")
+
+// ZswapStatus resource holds status of zwap subsystem.
+type ZswapStatus = typed.Resource[ZswapStatusSpec, ZswapStatusExtension]
+
+// ZswapStatusID is the ID of the singleton ZswapStatus resource.
+const ZswapStatusID resource.ID = "zswap"
+
+// ZswapStatusSpec is the spec for ZswapStatus resource.
+//
+//gotagsrewrite:gen
+type ZswapStatusSpec struct {
+ TotalSizeBytes uint64 `yaml:"totalSize" protobuf:"1"`
+ TotalSizeHuman string `yaml:"totalSizeHuman" protobuf:"2"`
+ StoredPages uint64 `yaml:"storedPages" protobuf:"3"`
+ PoolLimitHit uint64 `yaml:"poolLimitHit" protobuf:"4"`
+ RejectReclaimFail uint64 `yaml:"rejectReclaimFail" protobuf:"5"`
+ RejectAllocFail uint64 `yaml:"rejectAllocFail" protobuf:"6"`
+ RejectKmemcacheFail uint64 `yaml:"rejectKmemcacheFail" protobuf:"7"`
+ RejectCompressFail uint64 `yaml:"rejectCompressFail" protobuf:"8"`
+ RejectCompressPoor uint64 `yaml:"rejectCompressPoor" protobuf:"9"`
+ WrittenBackPages uint64 `yaml:"writtenBackPages" protobuf:"10"`
+}
+
+// NewZswapStatus initializes a ZswapStatus resource.
+func NewZswapStatus(namespace resource.Namespace, id resource.ID) *ZswapStatus {
+ return typed.NewResource[ZswapStatusSpec, ZswapStatusExtension](
+ resource.NewMetadata(namespace, ZswapStatusType, id, resource.VersionUndefined),
+ ZswapStatusSpec{},
+ )
+}
+
+// ZswapStatusExtension is auxiliary resource data for ZswapStatus.
+type ZswapStatusExtension struct{}
+
+// ResourceDefinition implements meta.ResourceDefinitionProvider interface.
+func (ZswapStatusExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
+ return meta.ResourceDefinitionSpec{
+ Type: ZswapStatusType,
+ Aliases: []resource.Type{},
+ DefaultNamespace: NamespaceName,
+ PrintColumns: []meta.PrintColumn{
+ {
+ Name: "Total Size",
+ JSONPath: "{.totalSizeHuman}",
+ },
+ {
+ Name: "Stored Pages",
+ JSONPath: "{.storedPages}",
+ },
+ {
+ Name: "Written Back",
+ JSONPath: "{.writtenBackPages}",
+ },
+ {
+ Name: "Pool Limit Hit",
+ JSONPath: "{.poolLimitHit}",
+ },
+ },
+ }
+}
+
+func init() {
+ proto.RegisterDefaultTypes()
+
+ err := protobuf.RegisterDynamic[ZswapStatusSpec](ZswapStatusType, &ZswapStatus{})
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/website/content/v1.11/reference/api.md b/website/content/v1.11/reference/api.md
index 4980d8b098a..2c7c18daef8 100644
--- a/website/content/v1.11/reference/api.md
+++ b/website/content/v1.11/reference/api.md
@@ -51,6 +51,7 @@ description: Talos gRPC API reference.
- [VolumeMountRequestSpec](#talos.resource.definitions.block.VolumeMountRequestSpec)
- [VolumeMountStatusSpec](#talos.resource.definitions.block.VolumeMountStatusSpec)
- [VolumeStatusSpec](#talos.resource.definitions.block.VolumeStatusSpec)
+ - [ZswapStatusSpec](#talos.resource.definitions.block.ZswapStatusSpec)
- [resource/definitions/cluster/cluster.proto](#resource/definitions/cluster/cluster.proto)
- [AffiliateSpec](#talos.resource.definitions.cluster.AffiliateSpec)
@@ -1302,6 +1303,30 @@ VolumeStatusSpec is the spec for VolumeStatus resource.
+
+
+
+### ZswapStatusSpec
+ZswapStatusSpec is the spec for ZswapStatus resource.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| total_size_bytes | [uint64](#uint64) | | |
+| total_size_human | [string](#string) | | |
+| stored_pages | [uint64](#uint64) | | |
+| pool_limit_hit | [uint64](#uint64) | | |
+| reject_reclaim_fail | [uint64](#uint64) | | |
+| reject_alloc_fail | [uint64](#uint64) | | |
+| reject_kmemcache_fail | [uint64](#uint64) | | |
+| reject_compress_fail | [uint64](#uint64) | | |
+| reject_compress_poor | [uint64](#uint64) | | |
+| written_back_pages | [uint64](#uint64) | | |
+
+
+
+
+
diff --git a/website/content/v1.11/reference/configuration/block/zswapconfig.md b/website/content/v1.11/reference/configuration/block/zswapconfig.md
new file mode 100644
index 00000000000..5efa3c4d0fc
--- /dev/null
+++ b/website/content/v1.11/reference/configuration/block/zswapconfig.md
@@ -0,0 +1,37 @@
+---
+description: |
+ ZswapConfig is a zswap (compressed memory) configuration document.
+ When zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.
+ The compressed pages are stored in a memory pool, which is used to avoid writing to disk
+ when the system is under memory pressure.
+title: ZswapConfig
+---
+
+
+
+
+
+
+
+
+
+
+
+{{< highlight yaml >}}
+apiVersion: v1alpha1
+kind: ZswapConfig
+maxPoolPercent: 25 # The maximum percent of memory that zswap can use.
+shrinkerEnabled: true # Enable the shrinker feature: kernel might move
+{{< /highlight >}}
+
+
+| Field | Type | Description | Value(s) |
+|-------|------|-------------|----------|
+|`maxPoolPercent` |int |The maximum percent of memory that zswap can use.
This is a percentage of the total system memory.
The value must be between 0 and 100. | |
+|`shrinkerEnabled` |bool |Enable the shrinker feature: kernel might move
cold pages from zswap to swap device to free up memory
for other use cases. | |
+
+
+
+
+
+
diff --git a/website/content/v1.11/schemas/config.schema.json b/website/content/v1.11/schemas/config.schema.json
index aa1de056032..3d259d8d792 100644
--- a/website/content/v1.11/schemas/config.schema.json
+++ b/website/content/v1.11/schemas/config.schema.json
@@ -370,6 +370,49 @@
],
"description": "VolumeConfig is a system volume configuration document.\\nNote: at the moment, only `EPHEMERAL` and `IMAGE-CACHE` system volumes are supported.\\n"
},
+ "block.ZswapConfigV1Alpha1": {
+ "properties": {
+ "apiVersion": {
+ "enum": [
+ "v1alpha1"
+ ],
+ "title": "apiVersion",
+ "description": "apiVersion is the API version of the resource.\n",
+ "markdownDescription": "apiVersion is the API version of the resource.",
+ "x-intellij-html-description": "\u003cp\u003eapiVersion is the API version of the resource.\u003c/p\u003e\n"
+ },
+ "kind": {
+ "enum": [
+ "ZswapConfig"
+ ],
+ "title": "kind",
+ "description": "kind is the kind of the resource.\n",
+ "markdownDescription": "kind is the kind of the resource.",
+ "x-intellij-html-description": "\u003cp\u003ekind is the kind of the resource.\u003c/p\u003e\n"
+ },
+ "maxPoolPercent": {
+ "type": "integer",
+ "title": "maxPoolPercent",
+ "description": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\n",
+ "markdownDescription": "The maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.",
+ "x-intellij-html-description": "\u003cp\u003eThe maximum percent of memory that zswap can use.\nThis is a percentage of the total system memory.\nThe value must be between 0 and 100.\u003c/p\u003e\n"
+ },
+ "shrinkerEnabled": {
+ "type": "boolean",
+ "title": "shrinkerEnabled",
+ "description": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\n",
+ "markdownDescription": "Enable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.",
+ "x-intellij-html-description": "\u003cp\u003eEnable the shrinker feature: kernel might move\ncold pages from zswap to swap device to free up memory\nfor other use cases.\u003c/p\u003e\n"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "required": [
+ "apiVersion",
+ "kind"
+ ],
+ "description": "ZswapConfig is a zswap (compressed memory) configuration document.\\nWhen zswap is enabled, Linux kernel compresses pages that would otherwise be swapped out to disk.\\nThe compressed pages are stored in a memory pool, which is used to avoid writing to disk\\nwhen the system is under memory pressure.\\n"
+ },
"extensions.ConfigFile": {
"properties": {
"content": {
@@ -4242,6 +4285,9 @@
{
"$ref": "#/$defs/block.VolumeConfigV1Alpha1"
},
+ {
+ "$ref": "#/$defs/block.ZswapConfigV1Alpha1"
+ },
{
"$ref": "#/$defs/extensions.ServiceConfigV1Alpha1"
},
diff --git a/website/content/v1.11/talos-guides/configuration/swap.md b/website/content/v1.11/talos-guides/configuration/swap.md
new file mode 100644
index 00000000000..82f8fc8e85a
--- /dev/null
+++ b/website/content/v1.11/talos-guides/configuration/swap.md
@@ -0,0 +1,79 @@
+---
+title: "Swap"
+description: "Guide on managing swap devices and zswap configuration in Talos Linux."
+---
+
+This guide provides an overview of the swap management features in Talos Linux.
+
+## Overview
+
+Swap devices are used to extend the available memory on a system by allowing the kernel to move inactive pages from RAM to disk.
+Swap might be useful to free up memory when running memory-intensive workloads, but it can also lead to performance degradation if used excessively.
+On other hand, moving inactive pages to swap can allow Linux to use more memory for buffers and caches, which can improve performance for workloads that benefit from caching.
+
+Zswap is a compressed cache for swap pages that can help reduce the performance impact of swapping by keeping frequently accessed pages in memory.
+Swap and zswap can be used together, but they can also be configured independently.
+
+Swap and zswap are disabled by default in Talos, but can be enabled through the configuration.
+
+## Swap Devices
+
+Swap devices can be configured in the [Talos machine configuration]({{< relref "../../reference/configuration/block/swapvolumeconfig" >}}) similar to how [User Volumes]({{< relref "disk-management#" >}}) are configured.
+As swap devices contain memory pages, it is recommended to enable disk encryption for swap devices to prevent sensitive data from being written to disk in plaintext.
+It is also recommended to use a separate disk for swap devices to avoid performance degradation on the system disk and other workloads.
+
+For example, to configure a swap device on a NVMe disk of size 4GiB, using static key for encryption, the following configuration patch can be used:
+
+```yaml
+apiVersion: v1alpha1
+kind: SwapVolumeConfig
+name: swap1
+provisioning:
+ diskSelector:
+ match: 'disk.transport == "nvme"'
+ minSize: 4GiB
+ maxSize: 4GiB
+encryption:
+ provider: luks2
+ keys:
+ - slot: 0
+ tpm: {}
+ - slot: 1
+ static:
+ passphrase: topsecret
+```
+
+Talos Linux will automatically provision the partition on the disk, label it as `s-swap1`, encrypt it using the provided key, and enable it as a swap device.
+
+Current swap status can be checked using `talosctl get swap` command:
+
+```shell
+$ talosctl -n 172.20.0.5 get swap
+NODE NAMESPACE TYPE ID VERSION DEVICE SIZE USED PRIORITY
+172.20.0.5 runtime SwapStatus /dev/nvme0n2p2 1 /dev/nvme0n2p2 3.9 GiB 100 MiB -2
+```
+
+Removing a `SwapVolumeConfig` document will remove the swap device from the system, but the partition will remain on the disk.
+To
+
+## Zswap
+
+Zswap is a compressed cache for swap pages that can help reduce the performance impact of swapping by keeping frequently accessed pages in memory.
+Zswap can be enabled in the [Talos machine configuration]({{< relref "../../reference/configuration/block/zswapconfig" >}}):
+
+```yaml
+apiVersion: v1alpha1
+kind: ZswapConfig
+maxPoolPercent: 20
+```
+
+This configuration enables zswap with a maximum pool size of 20% of the total system memory.
+To check the current zswap status, you can use the `talosctl get zswapstatus` command:
+
+```shell
+$ talosctl -n 172.20.0.5 get zswapstatus
+NODE NAMESPACE TYPE ID VERSION TOTAL SIZE STORED PAGES WRITTEN BACK POOL LIMIT HIT
+172.20.0.5 runtime ZswapStatus zswap 1 0 B 0 0 0
+```
+
+Removing a `ZswapConfig` document will disable zswap on the system.