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