common_linux.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // +build linux
  2. package common
  3. import (
  4. "context"
  5. "fmt"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "time"
  13. )
  14. func DoSysctrl(mib string) ([]string, error) {
  15. sysctl, err := exec.LookPath("sysctl")
  16. if err != nil {
  17. return []string{}, err
  18. }
  19. cmd := exec.Command(sysctl, "-n", mib)
  20. cmd.Env = getSysctrlEnv(os.Environ())
  21. out, err := cmd.Output()
  22. if err != nil {
  23. return []string{}, err
  24. }
  25. v := strings.Replace(string(out), "{ ", "", 1)
  26. v = strings.Replace(v, " }", "", 1)
  27. values := strings.Fields(v)
  28. return values, nil
  29. }
  30. func NumProcs() (uint64, error) {
  31. f, err := os.Open(HostProc())
  32. if err != nil {
  33. return 0, err
  34. }
  35. defer f.Close()
  36. list, err := f.Readdirnames(-1)
  37. if err != nil {
  38. return 0, err
  39. }
  40. var cnt uint64
  41. for _, v := range list {
  42. if _, err = strconv.ParseUint(v, 10, 64); err == nil {
  43. cnt++
  44. }
  45. }
  46. return cnt, nil
  47. }
  48. func BootTimeWithContext(ctx context.Context) (uint64, error) {
  49. system, role, err := Virtualization()
  50. if err != nil {
  51. return 0, err
  52. }
  53. statFile := "stat"
  54. if system == "lxc" && role == "guest" {
  55. // if lxc, /proc/uptime is used.
  56. statFile = "uptime"
  57. } else if system == "docker" && role == "guest" {
  58. // also docker, guest
  59. statFile = "uptime"
  60. }
  61. filename := HostProc(statFile)
  62. lines, err := ReadLines(filename)
  63. if err != nil {
  64. return 0, err
  65. }
  66. if statFile == "uptime" {
  67. if len(lines) != 1 {
  68. return 0, fmt.Errorf("wrong uptime format")
  69. }
  70. f := strings.Fields(lines[0])
  71. b, err := strconv.ParseFloat(f[0], 64)
  72. if err != nil {
  73. return 0, err
  74. }
  75. t := uint64(time.Now().Unix()) - uint64(b)
  76. return t, nil
  77. }
  78. if statFile == "stat" {
  79. for _, line := range lines {
  80. if strings.HasPrefix(line, "btime") {
  81. f := strings.Fields(line)
  82. if len(f) != 2 {
  83. return 0, fmt.Errorf("wrong btime format")
  84. }
  85. b, err := strconv.ParseInt(f[1], 10, 64)
  86. if err != nil {
  87. return 0, err
  88. }
  89. t := uint64(b)
  90. return t, nil
  91. }
  92. }
  93. }
  94. return 0, fmt.Errorf("could not find btime")
  95. }
  96. func Virtualization() (string, string, error) {
  97. return VirtualizationWithContext(context.Background())
  98. }
  99. // required variables for concurrency safe virtualization caching.
  100. var (
  101. cachedVirtMap map[string]string
  102. cachedVirtMutex sync.RWMutex
  103. cachedVirtOnce sync.Once
  104. )
  105. func VirtualizationWithContext(ctx context.Context) (string, string, error) {
  106. var system, role string
  107. // if cached already, return from cache
  108. cachedVirtMutex.RLock() // unlock won't be deferred so concurrent reads don't wait for long
  109. if cachedVirtMap != nil {
  110. cachedSystem, cachedRole := cachedVirtMap["system"], cachedVirtMap["role"]
  111. cachedVirtMutex.RUnlock()
  112. return cachedSystem, cachedRole, nil
  113. }
  114. cachedVirtMutex.RUnlock()
  115. filename := HostProc("xen")
  116. if PathExists(filename) {
  117. system = "xen"
  118. role = "guest" // assume guest
  119. if PathExists(filepath.Join(filename, "capabilities")) {
  120. contents, err := ReadLines(filepath.Join(filename, "capabilities"))
  121. if err == nil && StringsContains(contents, "control_d") {
  122. role = "host"
  123. }
  124. }
  125. }
  126. filename = HostProc("modules")
  127. if PathExists(filename) {
  128. contents, err := ReadLines(filename)
  129. if err == nil {
  130. switch {
  131. case StringsContains(contents, "kvm"):
  132. system = "kvm"
  133. role = "host"
  134. case StringsContains(contents, "vboxdrv"):
  135. system = "vbox"
  136. role = "host"
  137. case StringsContains(contents, "vboxguest"):
  138. system = "vbox"
  139. role = "guest"
  140. case StringsContains(contents, "vmware"):
  141. system = "vmware"
  142. role = "guest"
  143. }
  144. }
  145. }
  146. filename = HostProc("cpuinfo")
  147. if PathExists(filename) {
  148. contents, err := ReadLines(filename)
  149. if err == nil {
  150. if StringsContains(contents, "QEMU Virtual CPU") ||
  151. StringsContains(contents, "Common KVM processor") ||
  152. StringsContains(contents, "Common 32-bit KVM processor") {
  153. system = "kvm"
  154. role = "guest"
  155. }
  156. }
  157. }
  158. filename = HostProc("bus/pci/devices")
  159. if PathExists(filename) {
  160. contents, err := ReadLines(filename)
  161. if err == nil {
  162. if StringsContains(contents, "virtio-pci") {
  163. role = "guest"
  164. }
  165. }
  166. }
  167. filename = HostProc()
  168. if PathExists(filepath.Join(filename, "bc", "0")) {
  169. system = "openvz"
  170. role = "host"
  171. } else if PathExists(filepath.Join(filename, "vz")) {
  172. system = "openvz"
  173. role = "guest"
  174. }
  175. // not use dmidecode because it requires root
  176. if PathExists(filepath.Join(filename, "self", "status")) {
  177. contents, err := ReadLines(filepath.Join(filename, "self", "status"))
  178. if err == nil {
  179. if StringsContains(contents, "s_context:") ||
  180. StringsContains(contents, "VxID:") {
  181. system = "linux-vserver"
  182. }
  183. // TODO: guest or host
  184. }
  185. }
  186. if PathExists(filepath.Join(filename, "1", "environ")) {
  187. contents, err := ReadFile(filepath.Join(filename, "1", "environ"))
  188. if err == nil {
  189. if strings.Contains(contents, "container=lxc") {
  190. system = "lxc"
  191. role = "guest"
  192. }
  193. }
  194. }
  195. if PathExists(filepath.Join(filename, "self", "cgroup")) {
  196. contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
  197. if err == nil {
  198. switch {
  199. case StringsContains(contents, "lxc"):
  200. system = "lxc"
  201. role = "guest"
  202. case StringsContains(contents, "docker"):
  203. system = "docker"
  204. role = "guest"
  205. case StringsContains(contents, "machine-rkt"):
  206. system = "rkt"
  207. role = "guest"
  208. case PathExists("/usr/bin/lxc-version"):
  209. system = "lxc"
  210. role = "host"
  211. }
  212. }
  213. }
  214. if PathExists(HostEtc("os-release")) {
  215. p, _, err := GetOSRelease()
  216. if err == nil && p == "coreos" {
  217. system = "rkt" // Is it true?
  218. role = "host"
  219. }
  220. }
  221. // before returning for the first time, cache the system and role
  222. cachedVirtOnce.Do(func() {
  223. cachedVirtMutex.Lock()
  224. defer cachedVirtMutex.Unlock()
  225. cachedVirtMap = map[string]string{
  226. "system": system,
  227. "role": role,
  228. }
  229. })
  230. return system, role, nil
  231. }
  232. func GetOSRelease() (platform string, version string, err error) {
  233. contents, err := ReadLines(HostEtc("os-release"))
  234. if err != nil {
  235. return "", "", nil // return empty
  236. }
  237. for _, line := range contents {
  238. field := strings.Split(line, "=")
  239. if len(field) < 2 {
  240. continue
  241. }
  242. switch field[0] {
  243. case "ID": // use ID for lowercase
  244. platform = trimQuotes(field[1])
  245. case "VERSION":
  246. version = trimQuotes(field[1])
  247. }
  248. }
  249. return platform, version, nil
  250. }
  251. // trimQuotes removes quotes in the source string.
  252. func trimQuotes(s string) string {
  253. if len(s) >= 2 {
  254. if s[0] == '"' && s[len(s)-1] == '"' {
  255. return s[1 : len(s)-1]
  256. }
  257. }
  258. return s
  259. }