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  }