common_linux.go 6.3 KB
Newer Older
1
// +build linux
2 3

package common
4

5
import (
6 7
	"context"
	"fmt"
8 9
	"os"
	"os/exec"
10 11
	"path/filepath"
	"strconv"
12
	"strings"
13
	"sync"
14
	"time"
15 16 17
)

func DoSysctrl(mib string) ([]string, error) {
18
	sysctl, err := exec.LookPath("sysctl")
19 20 21
	if err != nil {
		return []string{}, err
	}
22
	cmd := exec.Command(sysctl, "-n", mib)
23
	cmd.Env = getSysctrlEnv(os.Environ())
24
	out, err := cmd.Output()
25 26 27 28
	if err != nil {
		return []string{}, err
	}
	v := strings.Replace(string(out), "{ ", "", 1)
V
Ville Skyttä 已提交
29 30
	v = strings.Replace(v, " }", "", 1)
	values := strings.Fields(v)
31 32 33

	return values, nil
}
34 35 36 37 38 39

func NumProcs() (uint64, error) {
	f, err := os.Open(HostProc())
	if err != nil {
		return 0, err
	}
T
tycho garen 已提交
40
	defer f.Close()
41

42
	list, err := f.Readdirnames(-1)
43 44 45
	if err != nil {
		return 0, err
	}
K
Kris Watts 已提交
46 47 48 49 50 51 52 53 54
	var cnt uint64

	for _, v := range list {
		if _, err = strconv.ParseUint(v, 10, 64); err == nil {
			cnt++
		}
	}

	return cnt, nil
55
}
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

func BootTimeWithContext(ctx context.Context) (uint64, error) {
	system, role, err := Virtualization()
	if err != nil {
		return 0, err
	}

	statFile := "stat"
	if system == "lxc" && role == "guest" {
		// if lxc, /proc/uptime is used.
		statFile = "uptime"
	} else if system == "docker" && role == "guest" {
		// also docker, guest
		statFile = "uptime"
	}

	filename := HostProc(statFile)
	lines, err := ReadLines(filename)
	if err != nil {
		return 0, err
	}

V
Ville Skyttä 已提交
78 79 80 81 82 83 84 85 86 87 88 89
	if statFile == "uptime" {
		if len(lines) != 1 {
			return 0, fmt.Errorf("wrong uptime format")
		}
		f := strings.Fields(lines[0])
		b, err := strconv.ParseFloat(f[0], 64)
		if err != nil {
			return 0, err
		}
		t := uint64(time.Now().Unix()) - uint64(b)
		return t, nil
	}
90 91 92 93 94 95 96 97 98 99 100
	if statFile == "stat" {
		for _, line := range lines {
			if strings.HasPrefix(line, "btime") {
				f := strings.Fields(line)
				if len(f) != 2 {
					return 0, fmt.Errorf("wrong btime format")
				}
				b, err := strconv.ParseInt(f[1], 10, 64)
				if err != nil {
					return 0, err
				}
101
				t := uint64(b)
102 103 104 105 106 107 108 109 110 111 112 113
				return t, nil
			}
		}
	}

	return 0, fmt.Errorf("could not find btime")
}

func Virtualization() (string, string, error) {
	return VirtualizationWithContext(context.Background())
}

V
Ville Skyttä 已提交
114
// required variables for concurrency safe virtualization caching.
115
var (
A
Atakan 已提交
116 117 118
	cachedVirtMap   map[string]string
	cachedVirtMutex sync.RWMutex
	cachedVirtOnce  sync.Once
119 120
)

121
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
A
Atakan 已提交
122 123
	var system, role string

A
Ata 已提交
124
	// if cached already, return from cache
A
Atakan 已提交
125 126 127 128 129
	cachedVirtMutex.RLock() // unlock won't be deferred so concurrent reads don't wait for long
	if cachedVirtMap != nil {
		cachedSystem, cachedRole := cachedVirtMap["system"], cachedVirtMap["role"]
		cachedVirtMutex.RUnlock()
		return cachedSystem, cachedRole, nil
A
Ata 已提交
130
	}
A
Atakan 已提交
131
	cachedVirtMutex.RUnlock()
132 133 134 135 136 137 138 139

	filename := HostProc("xen")
	if PathExists(filename) {
		system = "xen"
		role = "guest" // assume guest

		if PathExists(filepath.Join(filename, "capabilities")) {
			contents, err := ReadLines(filepath.Join(filename, "capabilities"))
V
Ville Skyttä 已提交
140 141
			if err == nil && StringsContains(contents, "control_d") {
				role = "host"
142 143 144 145 146 147 148 149
			}
		}
	}

	filename = HostProc("modules")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
150 151
			switch {
			case StringsContains(contents, "kvm"):
152 153
				system = "kvm"
				role = "host"
154
			case StringsContains(contents, "vboxdrv"):
155 156
				system = "vbox"
				role = "host"
157
			case StringsContains(contents, "vboxguest"):
158 159
				system = "vbox"
				role = "guest"
160
			case StringsContains(contents, "vmware"):
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
				system = "vmware"
				role = "guest"
			}
		}
	}

	filename = HostProc("cpuinfo")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "QEMU Virtual CPU") ||
				StringsContains(contents, "Common KVM processor") ||
				StringsContains(contents, "Common 32-bit KVM processor") {
				system = "kvm"
				role = "guest"
			}
		}
	}

180 181 182 183 184 185 186 187 188 189
	filename = HostProc("bus/pci/devices")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "virtio-pci") {
				role = "guest"
			}
		}
	}

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
	filename = HostProc()
	if PathExists(filepath.Join(filename, "bc", "0")) {
		system = "openvz"
		role = "host"
	} else if PathExists(filepath.Join(filename, "vz")) {
		system = "openvz"
		role = "guest"
	}

	// not use dmidecode because it requires root
	if PathExists(filepath.Join(filename, "self", "status")) {
		contents, err := ReadLines(filepath.Join(filename, "self", "status"))
		if err == nil {
			if StringsContains(contents, "s_context:") ||
				StringsContains(contents, "VxID:") {
				system = "linux-vserver"
			}
			// TODO: guest or host
		}
	}

211 212 213 214 215 216 217 218 219 220 221
	if PathExists(filepath.Join(filename, "1", "environ")) {
		contents, err := ReadFile(filepath.Join(filename, "1", "environ"))

		if err == nil {
			if strings.Contains(contents, "container=lxc") {
				system = "lxc"
				role = "guest"
			}
		}
	}

222 223 224
	if PathExists(filepath.Join(filename, "self", "cgroup")) {
		contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
		if err == nil {
225 226
			switch {
			case StringsContains(contents, "lxc"):
227 228
				system = "lxc"
				role = "guest"
229
			case StringsContains(contents, "docker"):
230 231
				system = "docker"
				role = "guest"
232
			case StringsContains(contents, "machine-rkt"):
233 234
				system = "rkt"
				role = "guest"
235
			case PathExists("/usr/bin/lxc-version"):
236 237 238 239 240 241 242 243 244 245 246 247 248
				system = "lxc"
				role = "host"
			}
		}
	}

	if PathExists(HostEtc("os-release")) {
		p, _, err := GetOSRelease()
		if err == nil && p == "coreos" {
			system = "rkt" // Is it true?
			role = "host"
		}
	}
A
Atakan 已提交
249

A
Ata 已提交
250
	// before returning for the first time, cache the system and role
A
Atakan 已提交
251 252 253 254 255 256 257 258 259
	cachedVirtOnce.Do(func() {
		cachedVirtMutex.Lock()
		defer cachedVirtMutex.Unlock()
		cachedVirtMap = map[string]string{
			"system": system,
			"role":   role,
		}
	})

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
	return system, role, nil
}

func GetOSRelease() (platform string, version string, err error) {
	contents, err := ReadLines(HostEtc("os-release"))
	if err != nil {
		return "", "", nil // return empty
	}
	for _, line := range contents {
		field := strings.Split(line, "=")
		if len(field) < 2 {
			continue
		}
		switch field[0] {
		case "ID": // use ID for lowercase
275
			platform = trimQuotes(field[1])
276
		case "VERSION":
277
			version = trimQuotes(field[1])
278 279 280 281
		}
	}
	return platform, version, nil
}
282

V
Ville Skyttä 已提交
283
// trimQuotes removes quotes in the source string.
284 285 286 287 288 289 290 291
func trimQuotes(s string) string {
	if len(s) >= 2 {
		if s[0] == '"' && s[len(s)-1] == '"' {
			return s[1 : len(s)-1]
		}
	}
	return s
}