github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/toolkit/net/net_darwin.go (about)

     1  //go:build darwin
     2  // +build darwin
     3  
     4  package net
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"os/exec"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/unionj-cloud/go-doudou/toolkit/internal/common"
    16  )
    17  
    18  var (
    19  	errNetstatHeader  = errors.New("Can't parse header of netstat output")
    20  	netstatLinkRegexp = regexp.MustCompile(`^<Link#(\d+)>$`)
    21  )
    22  
    23  const endOfLine = "\n"
    24  
    25  func parseNetstatLine(line string) (stat *IOCountersStat, linkID *uint, err error) {
    26  	var (
    27  		numericValue uint64
    28  		columns      = strings.Fields(line)
    29  	)
    30  
    31  	if columns[0] == "Name" {
    32  		err = errNetstatHeader
    33  		return
    34  	}
    35  
    36  	// try to extract the numeric value from <Link#123>
    37  	if subMatch := netstatLinkRegexp.FindStringSubmatch(columns[2]); len(subMatch) == 2 {
    38  		numericValue, err = strconv.ParseUint(subMatch[1], 10, 64)
    39  		if err != nil {
    40  			return
    41  		}
    42  		linkIDUint := uint(numericValue)
    43  		linkID = &linkIDUint
    44  	}
    45  
    46  	base := 1
    47  	numberColumns := len(columns)
    48  	// sometimes Address is omitted
    49  	if numberColumns < 12 {
    50  		base = 0
    51  	}
    52  	if numberColumns < 11 || numberColumns > 13 {
    53  		err = fmt.Errorf("Line %q do have an invalid number of columns %d", line, numberColumns)
    54  		return
    55  	}
    56  
    57  	parsed := make([]uint64, 0, 7)
    58  	vv := []string{
    59  		columns[base+3], // Ipkts == PacketsRecv
    60  		columns[base+4], // Ierrs == Errin
    61  		columns[base+5], // Ibytes == BytesRecv
    62  		columns[base+6], // Opkts == PacketsSent
    63  		columns[base+7], // Oerrs == Errout
    64  		columns[base+8], // Obytes == BytesSent
    65  	}
    66  	if len(columns) == 12 {
    67  		vv = append(vv, columns[base+10])
    68  	}
    69  
    70  	for _, target := range vv {
    71  		if target == "-" {
    72  			parsed = append(parsed, 0)
    73  			continue
    74  		}
    75  
    76  		if numericValue, err = strconv.ParseUint(target, 10, 64); err != nil {
    77  			return
    78  		}
    79  		parsed = append(parsed, numericValue)
    80  	}
    81  
    82  	stat = &IOCountersStat{
    83  		Name:        strings.Trim(columns[0], "*"), // remove the * that sometimes is on right on interface
    84  		PacketsRecv: parsed[0],
    85  		Errin:       parsed[1],
    86  		BytesRecv:   parsed[2],
    87  		PacketsSent: parsed[3],
    88  		Errout:      parsed[4],
    89  		BytesSent:   parsed[5],
    90  	}
    91  	if len(parsed) == 7 {
    92  		stat.Dropout = parsed[6]
    93  	}
    94  	return
    95  }
    96  
    97  type netstatInterface struct {
    98  	linkID *uint
    99  	stat   *IOCountersStat
   100  }
   101  
   102  func parseNetstatOutput(output string) ([]netstatInterface, error) {
   103  	var (
   104  		err   error
   105  		lines = strings.Split(strings.Trim(output, endOfLine), endOfLine)
   106  	)
   107  
   108  	// number of interfaces is number of lines less one for the header
   109  	numberInterfaces := len(lines) - 1
   110  
   111  	interfaces := make([]netstatInterface, numberInterfaces)
   112  	// no output beside header
   113  	if numberInterfaces == 0 {
   114  		return interfaces, nil
   115  	}
   116  
   117  	for index := 0; index < numberInterfaces; index++ {
   118  		nsIface := netstatInterface{}
   119  		if nsIface.stat, nsIface.linkID, err = parseNetstatLine(lines[index+1]); err != nil {
   120  			return nil, err
   121  		}
   122  		interfaces[index] = nsIface
   123  	}
   124  	return interfaces, nil
   125  }
   126  
   127  // map that hold the name of a network interface and the number of usage
   128  type mapInterfaceNameUsage map[string]uint
   129  
   130  func newMapInterfaceNameUsage(ifaces []netstatInterface) mapInterfaceNameUsage {
   131  	output := make(mapInterfaceNameUsage)
   132  	for index := range ifaces {
   133  		if ifaces[index].linkID != nil {
   134  			ifaceName := ifaces[index].stat.Name
   135  			usage, ok := output[ifaceName]
   136  			if ok {
   137  				output[ifaceName] = usage + 1
   138  			} else {
   139  				output[ifaceName] = 1
   140  			}
   141  		}
   142  	}
   143  	return output
   144  }
   145  
   146  func (min mapInterfaceNameUsage) isTruncated() bool {
   147  	for _, usage := range min {
   148  		if usage > 1 {
   149  			return true
   150  		}
   151  	}
   152  	return false
   153  }
   154  
   155  func (min mapInterfaceNameUsage) notTruncated() []string {
   156  	output := make([]string, 0)
   157  	for ifaceName, usage := range min {
   158  		if usage == 1 {
   159  			output = append(output, ifaceName)
   160  		}
   161  	}
   162  	return output
   163  }
   164  
   165  // example of `netstat -ibdnW` output on yosemite
   166  // Name  Mtu   Network       Address            Ipkts Ierrs     Ibytes    Opkts Oerrs     Obytes  Coll Drop
   167  // lo0   16384 <Link#1>                        869107     0  169411755   869107     0  169411755     0   0
   168  // lo0   16384 ::1/128     ::1                 869107     -  169411755   869107     -  169411755     -   -
   169  // lo0   16384 127           127.0.0.1         869107     -  169411755   869107     -  169411755     -   -
   170  func IOCounters(pernic bool) ([]IOCountersStat, error) {
   171  	return IOCountersWithContext(context.Background(), pernic)
   172  }
   173  
   174  func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
   175  	var (
   176  		ret      []IOCountersStat
   177  		retIndex int
   178  	)
   179  
   180  	netstat, err := exec.LookPath("netstat")
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	// try to get all interface metrics, and hope there won't be any truncated
   186  	out, err := invoke.CommandWithContext(ctx, netstat, "-ibdnW")
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	nsInterfaces, err := parseNetstatOutput(string(out))
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	ifaceUsage := newMapInterfaceNameUsage(nsInterfaces)
   197  	notTruncated := ifaceUsage.notTruncated()
   198  	ret = make([]IOCountersStat, len(notTruncated))
   199  
   200  	if !ifaceUsage.isTruncated() {
   201  		// no truncated interface name, return stats of all interface with <Link#...>
   202  		for index := range nsInterfaces {
   203  			if nsInterfaces[index].linkID != nil {
   204  				ret[retIndex] = *nsInterfaces[index].stat
   205  				retIndex++
   206  			}
   207  		}
   208  	} else {
   209  		// duplicated interface, list all interfaces
   210  		if out, err = invoke.CommandWithContext(ctx, "ifconfig", "-l"); err != nil {
   211  			return nil, err
   212  		}
   213  		interfaceNames := strings.Fields(strings.TrimRight(string(out), endOfLine))
   214  
   215  		// for each of the interface name, run netstat if we don't have any stats yet
   216  		for _, interfaceName := range interfaceNames {
   217  			truncated := true
   218  			for index := range nsInterfaces {
   219  				if nsInterfaces[index].linkID != nil && nsInterfaces[index].stat.Name == interfaceName {
   220  					// handle the non truncated name to avoid execute netstat for them again
   221  					ret[retIndex] = *nsInterfaces[index].stat
   222  					retIndex++
   223  					truncated = false
   224  					break
   225  				}
   226  			}
   227  			if truncated {
   228  				// run netstat with -I$ifacename
   229  				if out, err = invoke.CommandWithContext(ctx, netstat, "-ibdnWI"+interfaceName); err != nil {
   230  					return nil, err
   231  				}
   232  				parsedIfaces, err := parseNetstatOutput(string(out))
   233  				if err != nil {
   234  					return nil, err
   235  				}
   236  				if len(parsedIfaces) == 0 {
   237  					// interface had been removed since `ifconfig -l` had been executed
   238  					continue
   239  				}
   240  				for index := range parsedIfaces {
   241  					if parsedIfaces[index].linkID != nil {
   242  						ret = append(ret, *parsedIfaces[index].stat)
   243  						break
   244  					}
   245  				}
   246  			}
   247  		}
   248  	}
   249  
   250  	if pernic == false {
   251  		return getIOCountersAll(ret)
   252  	}
   253  	return ret, nil
   254  }
   255  
   256  // IOCountersByFile exists just for compatibility with Linux.
   257  func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) {
   258  	return IOCountersByFileWithContext(context.Background(), pernic, filename)
   259  }
   260  
   261  func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) {
   262  	return IOCounters(pernic)
   263  }
   264  
   265  func FilterCounters() ([]FilterStat, error) {
   266  	return FilterCountersWithContext(context.Background())
   267  }
   268  
   269  func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
   270  	return nil, common.ErrNotImplementedError
   271  }
   272  
   273  func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
   274  	return ConntrackStatsWithContext(context.Background(), percpu)
   275  }
   276  
   277  func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
   278  	return nil, common.ErrNotImplementedError
   279  }
   280  
   281  // NetProtoCounters returns network statistics for the entire system
   282  // If protocols is empty then all protocols are returned, otherwise
   283  // just the protocols in the list are returned.
   284  // Not Implemented for Darwin
   285  func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
   286  	return ProtoCountersWithContext(context.Background(), protocols)
   287  }
   288  
   289  func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) {
   290  	return nil, common.ErrNotImplementedError
   291  }