//go:build windows // +build windows /* Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package winstats import ( cadvisorapi "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" "testing" "unsafe" ) func TestGROUP_AFFINITY_Processors(t *testing.T) { tests := []struct { name string Mask uint64 Group uint16 want []int }{ { name: "empty", Mask: 0, Group: 0, want: []int{}, }, { name: "empty group 2", Mask: 0, Group: 1, want: []int{}, }, { name: "cpu 1 Group 0", Mask: 1, Group: 0, want: []int{0}, }, { name: "cpu 64 Group 0", Mask: 1 << 63, Group: 0, want: []int{63}, }, { name: "cpu 128 Group 1", Mask: 1 << 63, Group: 1, want: []int{127}, }, { name: "cpu 128 (Group 1)", Mask: 1 << 63, Group: 1, want: []int{127}, }, { name: "Mask 1 Group 2", Mask: 1, Group: 2, want: []int{128}, }, { name: "64 cpus group 0", Mask: 0xffffffffffffffff, Group: 0, want: makeRange(0, 63), }, { name: "64 cpus group 1", Mask: 0xffffffffffffffff, Group: 1, want: makeRange(64, 127), }, { name: "64 cpus group 1", Mask: 0xffffffffffffffff, Group: 1, want: makeRange(64, 127), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := GroupAffinity{ Mask: tt.Mask, Group: tt.Group, } assert.Equalf(t, tt.want, a.Processors(), "Processors()") }) } } // https://stackoverflow.com/a/39868255/697126 func makeRange(min, max int) []int { a := make([]int, max-min+1) for i := range a { a[i] = min + i } return a } func TestCpusToGroupAffinity(t *testing.T) { tests := []struct { name string cpus []int want map[int]*GroupAffinity }{ { name: "empty", want: map[int]*GroupAffinity{}, }, { name: "single cpu group 0", cpus: []int{0}, want: map[int]*GroupAffinity{ 0: { Mask: 1, Group: 0, }, }, }, { name: "single cpu group 0", cpus: []int{63}, want: map[int]*GroupAffinity{ 0: { Mask: 1 << 63, Group: 0, }, }, }, { name: "single cpu group 1", cpus: []int{64}, want: map[int]*GroupAffinity{ 1: { Mask: 1, Group: 1, }, }, }, { name: "multiple cpus same group", cpus: []int{0, 1, 2}, want: map[int]*GroupAffinity{ 0: { Mask: 1 | 2 | 4, // Binary OR to combine the masks Group: 0, }, }, }, { name: "multiple cpus different groups", cpus: []int{0, 64}, want: map[int]*GroupAffinity{ 0: { Mask: 1, Group: 0, }, 1: { Mask: 1, Group: 1, }, }, }, { name: "multiple cpus different groups", cpus: []int{0, 1, 2, 64, 65, 66}, want: map[int]*GroupAffinity{ 0: { Mask: 1 | 2 | 4, Group: 0, }, 1: { Mask: 1 | 2 | 4, Group: 1, }, }, }, { name: "64 cpus group 0", cpus: makeRange(0, 63), want: map[int]*GroupAffinity{ 0: { Mask: 0xffffffffffffffff, // All 64 bits set Group: 0, }, }, }, { name: "64 cpus group 1", cpus: makeRange(64, 127), want: map[int]*GroupAffinity{ 1: { Mask: 0xffffffffffffffff, // All 64 bits set Group: 1, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equalf(t, tt.want, CpusToGroupAffinity(tt.cpus), "CpusToGroupAffinity(%v)", tt.cpus) }) } } func Test_convertWinApiToCadvisorApi(t *testing.T) { tests := []struct { name string buffer []byte expectedNumOfCores int expectedNumOfSockets int expectedNodes []cadvisorapi.Node wantErr bool }{ { name: "empty", buffer: []byte{}, expectedNumOfCores: 0, expectedNumOfSockets: 0, expectedNodes: []cadvisorapi.Node{}, wantErr: false, }, { name: "single core", buffer: createProcessorRelationships([]int{0}), expectedNumOfCores: 1, expectedNumOfSockets: 1, expectedNodes: []cadvisorapi.Node{ { Id: 0, Cores: []cadvisorapi.Core{ { Id: 1, Threads: []int{0}, }, }, }, }, wantErr: false, }, { name: "single core, multiple cpus", buffer: createProcessorRelationships([]int{0, 1, 2}), expectedNumOfCores: 1, expectedNumOfSockets: 1, expectedNodes: []cadvisorapi.Node{ { Id: 0, Cores: []cadvisorapi.Core{ { Id: 1, Threads: []int{0, 1, 2}, }, }, }, }, wantErr: false, }, { name: "single core, multiple groups", buffer: createProcessorRelationships([]int{0, 64}), expectedNumOfCores: 1, expectedNumOfSockets: 1, expectedNodes: []cadvisorapi.Node{ { Id: 0, Cores: []cadvisorapi.Core{ { Id: 1, Threads: []int{0, 64}, }, }, }, }, wantErr: false, }, { name: "buffer to small", buffer: createProcessorRelationships([]int{0, 64})[:48], expectedNumOfCores: 1, expectedNumOfSockets: 1, expectedNodes: []cadvisorapi.Node{ { Id: 0, Cores: []cadvisorapi.Core{ { Id: 1, Threads: []int{0, 64}, }, }, }, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { numOfCores, numOfSockets, nodes, err := convertWinApiToCadvisorApi(tt.buffer) if tt.wantErr { assert.Error(t, err) return } assert.Equalf(t, tt.expectedNumOfCores, numOfCores, "num of cores") assert.Equalf(t, tt.expectedNumOfSockets, numOfSockets, "num of sockets") for node := range nodes { assert.Equalf(t, tt.expectedNodes[node].Id, nodes[node].Id, "node id") for core := range nodes[node].Cores { assert.Equalf(t, tt.expectedNodes[node].Cores[core].Id, nodes[node].Cores[core].Id, "core id") assert.Equalf(t, len(tt.expectedNodes[node].Cores[core].Threads), len(nodes[node].Cores[core].Threads), "num of threads") for _, thread := range nodes[node].Cores[core].Threads { assert.Truef(t, containsThread(tt.expectedNodes[node].Cores[core].Threads, thread), "thread %d", thread) } } } }) } } func containsThread(threads []int, thread int) bool { for _, t := range threads { if t == thread { return true } } return false } func genBuffer(infos ...systemLogicalProcessorInformationEx) []byte { var buffer []byte for _, info := range infos { buffer = append(buffer, structToBytes(info)...) } return buffer } func createProcessorRelationships(cpus []int) []byte { groups := CpusToGroupAffinity(cpus) grouplen := len(groups) groupAffinities := make([]GroupAffinity, 0, grouplen) for _, group := range groups { groupAffinities = append(groupAffinities, *group) } return genBuffer(systemLogicalProcessorInformationEx{ Relationship: uint32(relationProcessorCore), Size: uint32(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_SIZE + PROCESSOR_RELATIONSHIP_SIZE + (GROUP_AFFINITY_SIZE * grouplen)), data: processorRelationship{ Flags: 0, EfficiencyClass: 0, Reserved: [20]byte{}, GroupCount: uint16(grouplen), GroupMasks: groupAffinities, }, }, systemLogicalProcessorInformationEx{ Relationship: uint32(relationNumaNode), Size: uint32(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_SIZE + NUMA_NODE_RELATIONSHIP_SIZE + (GROUP_AFFINITY_SIZE * grouplen)), data: numaNodeRelationship{ NodeNumber: 0, Reserved: [18]byte{}, GroupCount: uint16(grouplen), GroupMasks: groupAffinities, }}, systemLogicalProcessorInformationEx{ Relationship: uint32(relationProcessorPackage), Size: uint32(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_SIZE + PROCESSOR_RELATIONSHIP_SIZE + (GROUP_AFFINITY_SIZE * grouplen)), data: processorRelationship{ Flags: 0, EfficiencyClass: 0, Reserved: [20]byte{}, GroupCount: uint16(grouplen), GroupMasks: groupAffinities, }, }) } const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_SIZE = 8 const PROCESSOR_RELATIONSHIP_SIZE = 24 const NUMA_NODE_RELATIONSHIP_SIZE = 24 const GROUP_AFFINITY_SIZE = int(unsafe.Sizeof(GroupAffinity{})) // this one is known at compile time func structToBytes(info systemLogicalProcessorInformationEx) []byte { var pri []byte = (*(*[SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_SIZE]byte)(unsafe.Pointer(&info)))[:SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_SIZE] switch info.data.(type) { case processorRelationship: rel := info.data.(processorRelationship) var prBytes []byte = (*(*[PROCESSOR_RELATIONSHIP_SIZE]byte)(unsafe.Pointer(&rel)))[:PROCESSOR_RELATIONSHIP_SIZE] pri = append(pri, prBytes...) groupAffinities := rel.GroupMasks.([]GroupAffinity) for _, groupAffinity := range groupAffinities { var groupByte []byte = (*(*[GROUP_AFFINITY_SIZE]byte)(unsafe.Pointer(&groupAffinity)))[:] pri = append(pri, groupByte...) } case numaNodeRelationship: numa := info.data.(numaNodeRelationship) var nameBytes []byte = (*(*[NUMA_NODE_RELATIONSHIP_SIZE]byte)(unsafe.Pointer(&numa)))[:NUMA_NODE_RELATIONSHIP_SIZE] pri = append(pri, nameBytes...) groupAffinities := numa.GroupMasks.([]GroupAffinity) for _, groupAffinity := range groupAffinities { var groupByte []byte = (*(*[GROUP_AFFINITY_SIZE]byte)(unsafe.Pointer(&groupAffinity)))[:] pri = append(pri, groupByte...) } } return pri }