From c880835c809c2a02f0bb6d0450d15df042a50781 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 11 Jun 2025 13:41:47 +0400 Subject: [PATCH] feat: implement zswap support Zswap allows to compress pages in memory before they hit the actual swap device. Both swap and zswap (or either one of these) can be enabled. Fixes #10675 Signed-off-by: Andrey Smirnov --- api/resource/definitions/block/block.proto | 14 + .../talos/cgroupsprinter/presets/swap.yaml | 6 + hack/release.toml | 7 + hack/test/patches/ephemeral-nvme.yaml | 5 + .../pkg/controllers/block/zswap_config.go | 115 ++++++ .../pkg/controllers/block/zswap_status.go | 131 ++++++ .../runtime/v1alpha2/v1alpha2_controller.go | 4 + .../pkg/runtime/v1alpha2/v1alpha2_state.go | 1 + internal/app/machined/pkg/startup/cgroups.go | 14 +- internal/integration/api/volumes.go | 23 ++ internal/pkg/cgroups/cgroups.go | 10 + internal/pkg/mount/v2/pseudo.go | 1 + .../resource/definitions/block/block.pb.go | 174 +++++++- .../definitions/block/block_vtproto.pb.go | 380 ++++++++++++++++++ pkg/machinery/config/config/config.go | 1 + pkg/machinery/config/config/volume.go | 7 + pkg/machinery/config/container/container.go | 10 + .../config/schemas/config.schema.json | 46 +++ pkg/machinery/config/types/block/block.go | 4 +- pkg/machinery/config/types/block/block_doc.go | 30 ++ .../config/types/block/deep_copy.generated.go | 16 +- .../block/testdata/zswapconfig_full.yaml | 4 + .../types/block/testdata/zswapconfig_min.yaml | 2 + .../config/types/block/zswap_config.go | 127 ++++++ .../config/types/block/zswap_config_test.go | 141 +++++++ pkg/machinery/resources/block/block.go | 2 +- pkg/machinery/resources/block/block_test.go | 1 + .../resources/block/deep_copy.generated.go | 8 +- pkg/machinery/resources/block/zswap_status.go | 86 ++++ website/content/v1.11/reference/api.md | 25 ++ .../configuration/block/zswapconfig.md | 37 ++ .../content/v1.11/schemas/config.schema.json | 46 +++ .../v1.11/talos-guides/configuration/swap.md | 134 ++++++ 33 files changed, 1579 insertions(+), 33 deletions(-) create mode 100644 internal/app/machined/pkg/controllers/block/zswap_config.go create mode 100644 internal/app/machined/pkg/controllers/block/zswap_status.go create mode 100644 pkg/machinery/config/types/block/testdata/zswapconfig_full.yaml create mode 100644 pkg/machinery/config/types/block/testdata/zswapconfig_min.yaml create mode 100644 pkg/machinery/config/types/block/zswap_config.go create mode 100644 pkg/machinery/config/types/block/zswap_config_test.go create mode 100644 pkg/machinery/resources/block/zswap_status.go create mode 100644 website/content/v1.11/reference/configuration/block/zswapconfig.md create mode 100644 website/content/v1.11/talos-guides/configuration/swap.md diff --git a/api/resource/definitions/block/block.proto b/api/resource/definitions/block/block.proto index 1a774d104..10ef0b465 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 6ebcded28..7133299fe 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 7e2bdfd8c..51fa15cb4 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -69,6 +69,13 @@ Talos now validates Kubernetes version in the image submitted in the machine con Previously this check was performed only on upgrade, but now it is consistently applied to upgrade, initial provisioning, and machine configuration updates. This implies that all image references should contain the tag, even if the image is pinned by digest. +""" + + [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 e0df1a726..8eb4c385e 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 000000000..91eec9f60 --- /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 000000000..aba1e37a6 --- /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 d5dc8c014..f17ea8cc8 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 25e97752e..ae1bdfd00 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 cce62e5ea..57d067bf4 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 3f42d73a6..cf35a15db 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 dee24ad23..47d86f8c4 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 0ecd6ced9..863ccf22f 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 4d1b02ac2..5553c0fb6 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 ad2c38586..396d32715 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 7303cc543..f616668a1 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 6ae6dab8d..e222bc06e 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 802d2fd6e..ab80cbe80 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 aa1de0560..3d259d8d7 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 f054ae4b0..ce9575084 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 417ed2577..e1055d232 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 4710a21f0..47ad4e53e 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 000000000..156bc23ed --- /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 000000000..b3c94e47e --- /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 000000000..52ba46b69 --- /dev/null +++ b/pkg/machinery/config/types/block/zswap_config.go @@ -0,0 +1,127 @@ +// 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 000000000..93cfde20d --- /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 de6fa1cf0..bb235ffd5 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 22312ff71..9e4ffcb5a 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 f8835e785..47e458ae9 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 000000000..8e7d76ed5 --- /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 4980d8b09..2c7c18dae 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 000000000..5efa3c4d0 --- /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 aa1de0560..3d259d8d7 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 000000000..7084ff861 --- /dev/null +++ b/website/content/v1.11/talos-guides/configuration/swap.md @@ -0,0 +1,134 @@ +--- +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 wipe the disk data, and make it allocatable again, use the following command (replace `nvme0n2p2` with the actual device name): + +```bash +talosctl wipe disk nvme0n2p2 --drop-partition +``` + +## 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. + +## Kubernetes and Swap + +Kubernetes by default [does not allow swap to be used by containers](https://kubernetes.io/blog/2025/03/25/swap-linux-improvements/), as it can lead to performance issues and unpredictable behavior. + +At the very minimum, enable swap usage in the `kubelet` configuration with the following machine configuration patch: + +```yaml +machine: + kubelet: + extraConfig: + memorySwap: + swapBehavior: LimitedSwap +``` + +Current swap and zwap usage and limits can be checked using the `talosctl cgroups` [command]({{< relref "../../advanced/cgroups-analysis" >}}): + +```shell +$ talosctl cgroups --preset=swap +NAME SwapCurrent SwapPeak SwapHigh SwapMax ZswapCurrent ZswapMax ZswapWriteback +. unset unset unset unset unset unset 1 +├──init 0 B 0 B max max 0 B max 1 +├──kubepods 0 B 0 B max max 0 B max 1 +│ ├──besteffort 0 B 0 B max max 0 B max 1 +│ │ └──kube-system/kube-proxy-5gwvz 0 B 0 B max max 0 B max 1 +│ │ ├──kube-proxy 0 B 0 B max 0 B 0 B max 1 +│ │ └──sandbox 0 B 0 B max max 0 B max 1 +│ └──burstable 0 B 0 B max max 0 B max 1 +│ ├──kube-system/coredns-78d87fb69b-qd6xd 0 B 0 B max max 0 B max 1 +│ │ ├──coredns 0 B 0 B max 0 B 0 B max 1 +│ │ └──sandbox 0 B 0 B max max 0 B max 1 +│ ├──kube-system/coredns-78d87fb69b-z8xj2 0 B 0 B max max 0 B max 1 +│ │ ├──coredns 0 B 0 B max 0 B 0 B max 1 +│ │ └──sandbox 0 B 0 B max max 0 B max 1 +│ └──kube-system/kube-flannel-qqd8v 0 B 0 B max max 0 B max 1 +│ ├──kube-flannel 0 B 0 B max 0 B 0 B max 1 +│ └──sandbox 0 B 0 B max max 0 B max 1 +├──podruntime 0 B 0 B max max 0 B max 1 +│ ├──kubelet 0 B 0 B max max 0 B max 1 +│ └──runtime 0 B 0 B max max 0 B max 1 +└──system 0 B 0 B max max 0 B max 1 + ├──apid 0 B 0 B max 0 B 0 B max 1 + ├──dashboard 0 B 0 B max max 0 B max 1 + ├──runtime 0 B 0 B max max 0 B max 1 + ├──trustd 0 B 0 B max 0 B 0 B max 1 + └──udevd 0 B 0 B max max 0 B max 1 +``` + +If `SwapMax` is set to `0 B`, it means that swap is not enabled for this cgroup (container/pod). +Current swap and zswap usage can be seen in the `SwapCurrent` and `ZswapCurrent` columns, respectively.