github.com/google/cadvisor@v0.49.1/utils/cpuload/netlink/netlink.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package netlink 16 17 import ( 18 "bytes" 19 "encoding/binary" 20 "fmt" 21 "os" 22 "syscall" 23 24 "golang.org/x/sys/unix" 25 26 info "github.com/google/cadvisor/info/v1" 27 ) 28 29 var ( 30 // TODO(rjnagal): Verify and fix for other architectures. 31 32 Endian = binary.LittleEndian 33 ) 34 35 type genMsghdr struct { 36 Command uint8 37 Version uint8 38 Reserved uint16 39 } 40 41 type netlinkMessage struct { 42 Header syscall.NlMsghdr 43 GenHeader genMsghdr 44 Data []byte 45 } 46 47 func (m netlinkMessage) toRawMsg() (rawmsg syscall.NetlinkMessage) { 48 rawmsg.Header = m.Header 49 w := bytes.NewBuffer([]byte{}) 50 binary.Write(w, Endian, m.GenHeader) 51 w.Write(m.Data) 52 rawmsg.Data = w.Bytes() 53 return rawmsg 54 } 55 56 type loadStatsResp struct { 57 Header syscall.NlMsghdr 58 GenHeader genMsghdr 59 Stats info.LoadStats 60 } 61 62 // Return required padding to align 'size' to 'alignment'. 63 func padding(size int, alignment int) int { 64 unalignedPart := size % alignment 65 return (alignment - unalignedPart) % alignment 66 } 67 68 // Get family id for taskstats subsystem. 69 func getFamilyID(conn *Connection) (uint16, error) { 70 msg := prepareFamilyMessage() 71 err := conn.WriteMessage(msg.toRawMsg()) 72 if err != nil { 73 return 0, err 74 } 75 76 resp, err := conn.ReadMessage() 77 if err != nil { 78 return 0, err 79 } 80 id, err := parseFamilyResp(resp) 81 if err != nil { 82 return 0, err 83 } 84 return id, nil 85 } 86 87 // Append an attribute to the message. 88 // Adds attribute info (length and type), followed by the data and necessary padding. 89 // Can be called multiple times to add attributes. Only fixed size and string type 90 // attributes are handled. We don't need nested attributes for task stats. 91 func addAttribute(buf *bytes.Buffer, attrType uint16, data interface{}, dataSize int) { 92 attr := syscall.RtAttr{ 93 Len: syscall.SizeofRtAttr, 94 Type: attrType, 95 } 96 attr.Len += uint16(dataSize) 97 binary.Write(buf, Endian, attr) 98 switch data := data.(type) { 99 case string: 100 binary.Write(buf, Endian, []byte(data)) 101 buf.WriteByte(0) // terminate 102 default: 103 binary.Write(buf, Endian, data) 104 } 105 for i := 0; i < padding(int(attr.Len), syscall.NLMSG_ALIGNTO); i++ { 106 buf.WriteByte(0) 107 } 108 } 109 110 // Prepares the message and generic headers and appends attributes as data. 111 func prepareMessage(headerType uint16, cmd uint8, attributes []byte) (msg netlinkMessage) { 112 msg.Header.Type = headerType 113 msg.Header.Flags = syscall.NLM_F_REQUEST 114 msg.GenHeader.Command = cmd 115 msg.GenHeader.Version = 0x1 116 msg.Data = attributes 117 return msg 118 } 119 120 // Prepares message to query family id for task stats. 121 func prepareFamilyMessage() (msg netlinkMessage) { 122 buf := bytes.NewBuffer([]byte{}) 123 addAttribute(buf, unix.CTRL_ATTR_FAMILY_NAME, unix.TASKSTATS_GENL_NAME, len(unix.TASKSTATS_GENL_NAME)+1) 124 return prepareMessage(unix.GENL_ID_CTRL, unix.CTRL_CMD_GETFAMILY, buf.Bytes()) 125 } 126 127 // Prepares message to query task stats for a task group. 128 func prepareCmdMessage(id uint16, cfd uintptr) (msg netlinkMessage) { 129 buf := bytes.NewBuffer([]byte{}) 130 addAttribute(buf, unix.CGROUPSTATS_CMD_ATTR_FD, uint32(cfd), 4) 131 return prepareMessage(id, unix.CGROUPSTATS_CMD_GET, buf.Bytes()) 132 } 133 134 // Extracts returned family id from the response. 135 func parseFamilyResp(msg syscall.NetlinkMessage) (uint16, error) { 136 m := new(netlinkMessage) 137 m.Header = msg.Header 138 err := verifyHeader(msg) 139 if err != nil { 140 return 0, err 141 } 142 buf := bytes.NewBuffer(msg.Data) 143 // extract generic header from data. 144 err = binary.Read(buf, Endian, &m.GenHeader) 145 if err != nil { 146 return 0, err 147 } 148 id := uint16(0) 149 // Extract attributes. kernel reports family name, id, version, etc. 150 // Scan till we find id. 151 for buf.Len() > syscall.SizeofRtAttr { 152 var attr syscall.RtAttr 153 err = binary.Read(buf, Endian, &attr) 154 if err != nil { 155 return 0, err 156 } 157 if attr.Type == unix.CTRL_ATTR_FAMILY_ID { 158 err = binary.Read(buf, Endian, &id) 159 if err != nil { 160 return 0, err 161 } 162 return id, nil 163 } 164 payload := int(attr.Len) - syscall.SizeofRtAttr 165 skipLen := payload + padding(payload, syscall.SizeofRtAttr) 166 name := make([]byte, skipLen) 167 err = binary.Read(buf, Endian, name) 168 if err != nil { 169 return 0, err 170 } 171 } 172 return 0, fmt.Errorf("family id not found in the response") 173 } 174 175 // Extract task stats from response returned by kernel. 176 func parseLoadStatsResp(msg syscall.NetlinkMessage) (*loadStatsResp, error) { 177 m := new(loadStatsResp) 178 m.Header = msg.Header 179 err := verifyHeader(msg) 180 if err != nil { 181 return m, err 182 } 183 buf := bytes.NewBuffer(msg.Data) 184 // Scan the general header. 185 err = binary.Read(buf, Endian, &m.GenHeader) 186 if err != nil { 187 return m, err 188 } 189 // cgroup stats response should have just one attribute. 190 // Read it directly into the stats structure. 191 var attr syscall.RtAttr 192 err = binary.Read(buf, Endian, &attr) 193 if err != nil { 194 return m, err 195 } 196 err = binary.Read(buf, Endian, &m.Stats) 197 if err != nil { 198 return m, err 199 } 200 return m, err 201 } 202 203 // Verify and return any error reported by kernel. 204 func verifyHeader(msg syscall.NetlinkMessage) error { 205 switch msg.Header.Type { 206 case syscall.NLMSG_DONE: 207 return fmt.Errorf("expected a response, got nil") 208 case syscall.NLMSG_ERROR: 209 buf := bytes.NewBuffer(msg.Data) 210 var errno int32 211 err := binary.Read(buf, Endian, errno) 212 if err != nil { 213 return err 214 } 215 return fmt.Errorf("netlink request failed with error %s", syscall.Errno(-errno)) 216 } 217 return nil 218 } 219 220 // Get load stats for a task group. 221 // id: family id for taskstats. 222 // cfd: open file to path to the cgroup directory under cpu hierarchy. 223 // conn: open netlink connection used to communicate with kernel. 224 func getLoadStats(id uint16, cfd *os.File, conn *Connection) (info.LoadStats, error) { 225 msg := prepareCmdMessage(id, cfd.Fd()) 226 err := conn.WriteMessage(msg.toRawMsg()) 227 if err != nil { 228 return info.LoadStats{}, err 229 } 230 231 resp, err := conn.ReadMessage() 232 if err != nil { 233 return info.LoadStats{}, err 234 } 235 236 parsedmsg, err := parseLoadStatsResp(resp) 237 if err != nil { 238 return info.LoadStats{}, err 239 } 240 return parsedmsg.Stats, nil 241 }