find.go 5.12 KB
Newer Older
liming6's avatar
liming6 committed
1
2
3
package docker

import (
4
	"os/exec"
5
	"strconv"
liming6's avatar
liming6 committed
6
7
8
9
10
11
12

	"context"
	"errors"
	"regexp"
	"strings"
	"sync"
	"time"
13
14
15

	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/client"
liming6's avatar
liming6 committed
16
17
18
19
20
21
22
)

var (
	ReDocker                      = regexp.MustCompile(`^.*docker[-/]([0-9a-z]*)(?:|.*)`)
	ContainerInfo *ContainersInfo = nil
)

liming6's avatar
liming6 committed
23
24
25
26
27
28
29
30
31
32
33
34
35
func NewContainersInfo() *ContainersInfo {
	ContainerInfo = &ContainersInfo{
		inspectInfo: make(map[string]container.InspectResponse),
		listInfo:    make(map[string]container.Summary),
		topInfo:     make(map[string]container.TopResponse),
		time:        time.Now(),
		inspectLock: sync.RWMutex{},
		topLock:     sync.RWMutex{},
		listLock:    sync.RWMutex{},
	}
	return ContainerInfo
}

liming6's avatar
liming6 committed
36
type ContainersInfo struct {
liming6's avatar
liming6 committed
37
	time        time.Time // 记录写入Info的时间
liming6's avatar
liming6 committed
38
	inspectInfo map[string]container.InspectResponse
liming6's avatar
liming6 committed
39
	inspectLock sync.RWMutex
liming6's avatar
liming6 committed
40
	listInfo    map[string]container.Summary
liming6's avatar
liming6 committed
41
	listLock    sync.RWMutex
42
	topInfo     map[string]container.TopResponse
liming6's avatar
liming6 committed
43
	topLock     sync.RWMutex
44
45
46
}

type ContainerPsInfo struct {
47
	Pid  int32
48
	Ppid int32
49
	Uid  string
50
	Cmd  string
51
52
}

53
func ParsePsInfo(topInfo map[string]container.TopResponse) (map[string][]ContainerPsInfo, error) {
54
55
56
	if topInfo == nil {
		return nil, errors.New("topInfo is nil")
	}
57
	result := make(map[string][]ContainerPsInfo)
58
	for cid, topResp := range topInfo {
59
60
		indexMap, t := make(map[string]int), 0
		result[cid] = make([]ContainerPsInfo, 0)
61
62
63
		for index, key := range topResp.Titles {
			switch strings.TrimSpace(strings.ToLower(key)) {
			case "pid":
64
				indexMap["pid"] = index
65
66
				t++
			case "ppid":
67
				indexMap["ppid"] = index
68
69
				t++
			case "uid":
70
				indexMap["uid"] = index
71
72
				t++
			case "cmd":
73
				indexMap["cmd"] = index
74
75
76
77
78
79
80
				t++
			default:
			}
			if t >= 4 {
				break
			}
		}
81
82
83
		for _, fields := range topResp.Processes {
			item := ContainerPsInfo{}
			if v, ok := indexMap["pid"]; ok {
84
				pid, err := strconv.ParseInt(fields[v], 10, 64)
85
86
87
88
				if err != nil {
					return nil, err
				}
				item.Pid = int32(pid)
89
			}
90
			if v, ok := indexMap["ppid"]; ok {
91
				ppid, err := strconv.ParseInt(fields[v], 10, 64)
92
93
94
				if err != nil {
					return nil, err
				}
95
				item.Ppid = int32(ppid)
96
			}
97
98
99
100
101
102
103
			if v, ok := indexMap["uid"]; ok {
				item.Uid = fields[v]
			}
			if v, ok := indexMap["cmd"]; ok {
				item.Cmd = fields[v]
			}
			result[cid] = append(result[cid], item)
104
105
106
		}
	}
	return result, nil
liming6's avatar
liming6 committed
107
108
109
}

func (info *ContainersInfo) Update() error {
liming6's avatar
liming6 committed
110
111
112
113
114
115
116
117
118
	info.listLock.Lock()
	info.topLock.Lock()
	info.inspectLock.Lock()
	defer func() {
		info.inspectLock.Unlock()
		info.topLock.Unlock()
		info.listLock.Unlock()
	}()
	i, s, t, err := getRunningContainerInfo()
liming6's avatar
liming6 committed
119
120
121
122
123
	if err != nil {
		return err
	}
	info.inspectInfo = i
	info.listInfo = s
124
	info.topInfo = t
liming6's avatar
liming6 committed
125
126
127
128
	info.time = time.Now()
	return nil
}

liming6's avatar
liming6 committed
129
130
131
func (info *ContainersInfo) GetInspectInfo(update bool) (map[string]container.InspectResponse, sync.Locker) {
	if update {
		info.Update()
liming6's avatar
liming6 committed
132
	}
liming6's avatar
liming6 committed
133
134
135
136
	rl := info.inspectLock.RLocker()
	rl.Lock()
	if info.inspectInfo == nil {
		return make(map[string]container.InspectResponse), rl
liming6's avatar
liming6 committed
137
	}
liming6's avatar
liming6 committed
138
	return info.inspectInfo, rl
liming6's avatar
liming6 committed
139
140
}

141
// GetProcessIdInDocker 获取所用容器的进程信息
liming6's avatar
liming6 committed
142
func (info *ContainersInfo) GetProcessIdInDocker(update bool) (map[string][]ContainerPsInfo, error) {
143
144
145
	if update {
		err := info.Update()
		if err != nil {
liming6's avatar
liming6 committed
146
			return nil, err
147
148
		}
	}
liming6's avatar
liming6 committed
149
	rl := info.topLock.RLocker()
150
151
152
153
	rl.Lock()
	i, err := ParsePsInfo(info.topInfo)
	rl.Unlock()
	rl = nil
liming6's avatar
liming6 committed
154
155
156
	if err != nil {
		return nil, err
	}
liming6's avatar
liming6 committed
157
	return i, nil
liming6's avatar
liming6 committed
158
159
}

liming6's avatar
liming6 committed
160
161
// getRunningContainerInfo 获取所有正在运行的docker容器的详细信息
func getRunningContainerInfo() (map[string]container.InspectResponse, map[string]container.Summary, map[string]container.TopResponse, error) {
162
	cli, err := GetDockerClient()
liming6's avatar
liming6 committed
163
	if err != nil {
164
		return nil, nil, nil, err
liming6's avatar
liming6 committed
165
166
167
168
169
170
	}
	defer func() {
		_ = cli.Close()
	}()
	containerSum, err := cli.ContainerList(context.Background(), client.ContainerListOptions{All: false})
	if err != nil {
171
		return nil, nil, nil, err
liming6's avatar
liming6 committed
172
173
174
	}
	inspects := make(map[string]container.InspectResponse)
	lists := make(map[string]container.Summary)
175
	tops := make(map[string]container.TopResponse)
liming6's avatar
liming6 committed
176
177
178
	for _, c := range containerSum {
		inspect, innerErr := cli.ContainerInspect(context.Background(), c.ID)
		if innerErr != nil {
179
			return nil, nil, nil, innerErr
liming6's avatar
liming6 committed
180
181
182
		}
		inspects[c.ID] = inspect
		lists[c.ID] = c
183
184
185
186
187
		topInfo, innerErr := cli.ContainerTop(context.Background(), c.ID, nil)
		if innerErr != nil {
			return nil, nil, nil, innerErr
		}
		tops[c.ID] = topInfo
liming6's avatar
liming6 committed
188
	}
189
	return inspects, lists, tops, nil
liming6's avatar
liming6 committed
190
}
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

var (
	reDockerApi = regexp.MustCompile(`(?i)^\s+api\s+version:\s+([0-9.]+).*$`)
)

func GetDockerAPIVersion() (string, error) {
	output, err := exec.Command("docker", "version").Output()
	if err != nil {
		return "", err
	}
	lines := strings.Split(strings.Trim(string(output), "\n"), "\n")
	for _, v := range lines {
		if reDockerApi.MatchString(v) {
			m := reDockerApi.FindStringSubmatch(v)
			return m[1], nil
		}
	}
	return "", nil
}

func GetDockerClient() (*client.Client, error) {
	ver, err := GetDockerAPIVersion()
	if err != nil {
		return client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	}
	return client.NewClientWithOpts(client.FromEnv, client.WithVersion(ver))
}