net_darwin.go 7.8 KB

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