net_darwin.go 7.7 KB

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