]> git.proxmox.com Git - pve-common.git/blob - src/PVE/CpuSet.pm
cpuset: allow empty cpusets
[pve-common.git] / src / PVE / CpuSet.pm
1 package PVE::CpuSet;
2
3 use strict;
4 use warnings;
5 use PVE::Tools;
6 use PVE::ProcFSTools;
7
8 sub new {
9 my ($class, $members) = @_;
10
11 $members //= {};
12 my $self = bless { members => $members }, $class;
13
14 return $self;
15 }
16
17 # Create a new set with the contents of a cgroup-v1 subdirectory.
18 sub new_from_cgroup {
19 my ($class, $cgroup, $effective) = @_;
20
21 return $class->new_from_path("/sys/fs/cgroup/cpuset/$cgroup", $effective);
22 }
23
24 # Create a new set from the contents of a complete path to a cgroup directory.
25 sub new_from_path {
26 my ($class, $path, $effective) = @_;
27
28 my $filename;
29 if ($effective) {
30 $filename = "$path/cpuset.effective_cpus";
31 if (!-e $filename) {
32 # cgroupv2:
33 $filename = "$path/cpuset.cpus.effective";
34 }
35 } else {
36 $filename = "$path/cpuset.cpus";
37 }
38
39 my $set_text = PVE::Tools::file_read_firstline($filename) // '';
40
41 my ($count, $members) = parse_cpuset($set_text);
42
43 return $class->new($members);
44 }
45
46 sub parse_cpuset {
47 my ($set_text) = @_;
48
49 my $members = {};
50 my $count = 0;
51
52 foreach my $part (split(/,/, $set_text)) {
53 if ($part =~ /^\s*(\d+)(?:-(\d+))?\s*$/) {
54 my ($from, $to) = ($1, $2);
55 $to //= $1;
56 die "invalid range: $part ($to < $from)\n" if $to < $from;
57 for (my $i = $from; $i <= $to; $i++) {
58 $members->{$i} = 1;
59 $count++;
60 };
61 } else {
62 die "invalid range: $part\n";
63 }
64 }
65
66 return ($count, $members);
67 }
68
69 sub write_to_cgroup {
70 my ($self, $cgroup) = @_;
71
72 my $filename = "/sys/fs/cgroup/cpuset/$cgroup/cpuset.cpus";
73
74 my $value = '';
75 my @members = $self->members();
76 foreach my $cpuid (@members) {
77 $value .= ',' if length($value);
78 $value .= $cpuid;
79 }
80
81 open(my $fh, '>', $filename) || die "failed to open '$filename' - $!\n";
82 PVE::Tools::safe_print($filename, $fh, "$value\n");
83 close($fh) || die "failed to close '$filename' - $!\n";
84 }
85
86 sub insert {
87 my ($self, @members) = @_;
88
89 my $count = 0;
90
91 foreach my $cpu (@members) {
92 next if $self->{members}->{$cpu};
93 $self->{members}->{$cpu} = 1;
94 $count++;
95 }
96
97 return $count;
98 }
99
100 sub delete {
101 my ($self, @members) = @_;
102
103 my $count = 0;
104
105 foreach my $cpu (@members) {
106 next if !$self->{members}->{$cpu};
107 delete $self->{members}->{$cpu};
108 $count++;
109 }
110
111 return $count;
112 }
113
114 sub has {
115 my ($self, $cpuid) = @_;
116
117 return $self->{members}->{$cpuid};
118 }
119
120 # members: this list is always sorted!
121 sub members {
122 my ($self) = @_;
123
124 return sort { $a <=> $b } keys %{$self->{members}};
125 }
126
127 sub size {
128 my ($self) = @_;
129
130 return scalar(keys %{$self->{members}});
131 }
132
133 sub is_equal {
134 my ($self, $set2) = @_;
135
136 my $members1 = $self->{members};
137 my $members2 = $set2->{members};
138
139 foreach my $id (keys %$members1) {
140 return 0 if !$members2->{$id};
141 }
142 foreach my $id (keys %$members2) {
143 return 0 if !$members1->{$id};
144 }
145
146 return 1;
147 }
148
149 sub short_string {
150 my ($self) = @_;
151
152 my @members = $self->members();
153
154 my $res = '';
155 my ($last, $next);
156 foreach my $cpu (@members) {
157 if (!defined($last)) {
158 $last = $next = $cpu;
159 } elsif (($next + 1) == $cpu) {
160 $next = $cpu;
161 } else {
162 $res .= ',' if length($res);
163 if ($last != $next) {
164 $res .= "$last-$next";
165 } else {
166 $res .= "$last";
167 }
168 $last = $next = $cpu;
169 }
170 }
171
172 if (defined($last)) {
173 $res .= ',' if length($res);
174 if ($last != $next) {
175 $res .= "$last-$next";
176 } else {
177 $res .= "$last";
178 }
179 }
180
181 return $res;
182 }
183
184 1;