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