summaryrefslogtreecommitdiff
path: root/guestmetric
diff options
context:
space:
mode:
Diffstat (limited to 'guestmetric')
-rw-r--r--guestmetric/guestmetric.go38
-rw-r--r--guestmetric/guestmetric_linux.go257
-rw-r--r--guestmetric/guestmetric_test.go63
3 files changed, 358 insertions, 0 deletions
diff --git a/guestmetric/guestmetric.go b/guestmetric/guestmetric.go
new file mode 100644
index 0000000..836f105
--- /dev/null
+++ b/guestmetric/guestmetric.go
@@ -0,0 +1,38 @@
+package guestmetric
+
+import (
+ "bytes"
+ "os/exec"
+)
+
+type GuestMetric map[string]string
+
+type CollectFunc func() (GuestMetric, error)
+
+type GuestMetricsCollector interface {
+ CollectOS() (GuestMetric, error)
+ CollectMisc() (GuestMetric, error)
+ CollectNetworkAddr() (GuestMetric, error)
+ CollectDisk() (GuestMetric, error)
+ CollectMemory() (GuestMetric, error)
+}
+
+func runCmd(name string, args ...string) (output string, err error) {
+ cmd := exec.Command(name, args...)
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ err = cmd.Run()
+ if err != nil {
+ return "", err
+ }
+ output = out.String()
+ return output, nil
+}
+
+func prefixKeys(prefix string, m GuestMetric) GuestMetric {
+ m1 := make(GuestMetric, 0)
+ for k, v := range m {
+ m1[prefix+k] = v
+ }
+ return m1
+}
diff --git a/guestmetric/guestmetric_linux.go b/guestmetric/guestmetric_linux.go
new file mode 100644
index 0000000..3970da4
--- /dev/null
+++ b/guestmetric/guestmetric_linux.go
@@ -0,0 +1,257 @@
+package guestmetric
+
+import (
+ xenstoreclient "../xenstoreclient"
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+type Collector struct {
+ Client xenstoreclient.XenStoreClient
+ Ballon bool
+ Debug bool
+}
+
+func (c *Collector) CollectOS() (GuestMetric, error) {
+ current := make(GuestMetric, 0)
+ f, err := os.OpenFile("/var/cache/xe-linux-distribution", os.O_RDONLY, 0666)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.Contains(line, "=") {
+ parts := strings.SplitN(line, "=", 2)
+ k := strings.TrimSpace(parts[0])
+ v := strings.TrimSpace(strings.Trim(strings.TrimSpace(parts[1]), "\""))
+ current[k] = v
+ }
+ }
+ return prefixKeys("data/", current), nil
+}
+
+func (c *Collector) CollectMisc() (GuestMetric, error) {
+ current := make(GuestMetric, 0)
+ if c.Ballon {
+ current["control/feature-balloon"] = "1"
+ } else {
+ current["control/feature-balloon"] = "0"
+ }
+ current["attr/PVAddons/Installed"] = "1"
+ current["attr/PVAddons/MajorVersion"] = "@PRODUCT_MAJOR_VERSION@"
+ current["attr/PVAddons/MinorVersion"] = "@PRODUCT_MINOR_VERSION@"
+ current["attr/PVAddons/MicroVersion"] = "@PRODUCT_MICRO_VERSION@"
+ current["attr/PVAddons/BuildVersion"] = "@NUMERIC_BUILD_NUMBER@"
+
+ return current, nil
+}
+
+func (c *Collector) CollectMemory() (GuestMetric, error) {
+ current := make(GuestMetric, 0)
+ f, err := os.OpenFile("/proc/meminfo", os.O_RDONLY, 0666)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ parts := regexp.MustCompile(`\w+`).FindAllString(scanner.Text(), -1)
+ switch parts[0] {
+ case "MemTotal":
+ current["meminfo_total"] = parts[1]
+ case "MemFree":
+ current["meminfo_free"] = parts[1]
+ }
+ }
+ return prefixKeys("data/", current), nil
+}
+
+func EnumNetworkAddresses(iface string) (GuestMetric, error) {
+ const (
+ IP_RE string = `(\d{1,3}\.){3}\d{1,3}`
+ IPV6_RE string = `[\da-f:]+[\da-f]`
+ )
+
+ var (
+ IP_IPV4_ADDR_RE = regexp.MustCompile(`inet\s*(` + IP_RE + `).*\se[a-zA-Z0-9]+\s`)
+ IP_IPV6_ADDR_RE = regexp.MustCompile(`inet6\s*(` + IPV6_RE + `)`)
+ IFCONFIG_IPV4_ADDR_RE = regexp.MustCompile(`inet addr:\s*(` + IP_RE + `)`)
+ IFCONFIG_IPV6_ADDR_RE = regexp.MustCompile(`inet6 addr:\s*(` + IPV6_RE + `)`)
+ )
+
+ d := make(GuestMetric, 0)
+
+ var v4re, v6re *regexp.Regexp
+ var out string
+ var err error
+ if out, err = runCmd("ip", "addr", "show", iface); err == nil {
+ v4re = IP_IPV4_ADDR_RE
+ v6re = IP_IPV6_ADDR_RE
+ } else if out, err = runCmd("ifconfig", iface); err == nil {
+ v4re = IFCONFIG_IPV4_ADDR_RE
+ v6re = IFCONFIG_IPV6_ADDR_RE
+ } else {
+ return nil, fmt.Errorf("Cannot found ip/ifconfig command")
+ }
+
+ m := v4re.FindAllStringSubmatch(out, -1)
+ if m != nil {
+ for _, parts := range m {
+ d["ip"] = parts[1]
+ }
+ }
+ m = v6re.FindAllStringSubmatch(out, -1)
+ if m != nil {
+ for i, parts := range m {
+ d[fmt.Sprintf("ipv6/%d/addr", i)] = parts[1]
+ }
+ }
+ return d, nil
+}
+
+func (c *Collector) CollectNetworkAddr() (GuestMetric, error) {
+ current := make(GuestMetric, 0)
+
+ paths, err := filepath.Glob("/sys/class/net/e*")
+ if err != nil {
+ return nil, err
+ }
+
+ for _, path := range paths {
+ iface := filepath.Base(path)
+ if addrs, err := EnumNetworkAddresses(iface); err == nil {
+ for tag, addr := range addrs {
+ current[fmt.Sprintf("%s/%s", iface, tag)] = addr
+ }
+ }
+ }
+ return prefixKeys("attr/", current), nil
+}
+
+func readSysfs(filename string) (string, error) {
+ f, err := os.OpenFile(filename, os.O_RDONLY, 0666)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ scanner.Scan()
+ return scanner.Text(), nil
+}
+
+func (c *Collector) CollectDisk() (GuestMetric, error) {
+ pi := make(GuestMetric, 0)
+
+ disks := make([]string, 0)
+ paths, err := filepath.Glob("/sys/block/*/device")
+ if err != nil {
+ return nil, err
+ }
+ for _, path := range paths {
+ disk := filepath.Base(strings.TrimSuffix(filepath.Dir(path), "/"))
+ disks = append(disks, disk)
+ }
+
+ var sortedDisks sort.StringSlice = disks
+ sortedDisks.Sort()
+
+ part_idx := 0
+ for _, disk := range sortedDisks[:] {
+ paths, err = filepath.Glob(fmt.Sprintf("/dev/%s?*", disk))
+ if err != nil {
+ return nil, err
+ }
+ for _, path := range paths {
+ p := filepath.Base(path)
+ line, err := readSysfs(fmt.Sprintf("/sys/block/%s/%s/size", disk, p))
+ if err != nil {
+ return nil, err
+ }
+ size, err := strconv.ParseInt(line, 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ blocksize := 512
+ if bs, err := readSysfs(fmt.Sprintf("/sys/block/%s/queue/physical_block_size", p)); err == nil {
+ if bs1, err := strconv.Atoi(bs); err == nil {
+ blocksize = bs1
+ }
+ }
+ real_dev := ""
+ if c.Client != nil {
+ nodename, err := readSysfs(fmt.Sprintf("/sys/block/%s/device/nodename", disk))
+ if err != nil {
+ return nil, err
+ }
+ backend, err := c.Client.Read(fmt.Sprintf("%s/backend", nodename))
+ if err != nil {
+ return nil, err
+ }
+ real_dev, err = c.Client.Read(fmt.Sprintf("%s/dev", backend))
+ if err != nil {
+ return nil, err
+ }
+ }
+ name := path
+ blkid, err := runCmd("blkid", "-s", "UUID", path)
+ if err != nil {
+ return nil, err
+ }
+ if strings.Contains(blkid, "=") {
+ parts := strings.SplitN(strings.TrimSpace(blkid), "=", 2)
+ name = fmt.Sprintf("%s(%s)", name, strings.Trim(parts[1], "\""))
+ }
+ i := map[string]string{
+ "extents/0": real_dev,
+ "name": name,
+ "size": strconv.FormatInt(size*int64(blocksize), 10),
+ }
+ output, err := runCmd("pvs", "--noheadings", "--units", "b", path)
+ if err == nil && output != "" {
+ parts := regexp.MustCompile(`\s+`).Split(output, -1)[1:]
+ i["free"] = strings.TrimSpace(parts[5])[:len(parts[5])-1]
+ i["filesystem"] = strings.TrimSpace(parts[2])
+ i["mount_points/0"] = "[LVM]"
+ } else {
+ output, err = runCmd("mount")
+ if err == nil {
+ m := regexp.MustCompile(`(?m)^(\S+) on (\S+) type (\S+)`).FindAllStringSubmatch(output, -1)
+ if m != nil {
+ for _, parts := range m {
+ if parts[1] == path {
+ i["mount_points/0"] = parts[2]
+ i["filesystem"] = parts[3]
+ break
+ }
+ }
+ }
+ }
+ output, err = runCmd("df", path)
+ if err == nil {
+ scanner := bufio.NewScanner(bytes.NewReader([]byte(output)))
+ scanner.Scan()
+ scanner.Scan()
+ parts := regexp.MustCompile(`\s+`).Split(scanner.Text(), -1)
+ free, err := strconv.ParseInt(parts[3], 10, 64)
+ if err == nil {
+ i["free"] = strconv.FormatInt(free*1024, 10)
+ }
+ }
+ }
+ for k, v := range i {
+ pi[fmt.Sprintf("data/volumes/%d/%s", part_idx, k)] = v
+ }
+ part_idx += 1
+ }
+ }
+ return pi, nil
+}
diff --git a/guestmetric/guestmetric_test.go b/guestmetric/guestmetric_test.go
new file mode 100644
index 0000000..dcc5384
--- /dev/null
+++ b/guestmetric/guestmetric_test.go
@@ -0,0 +1,63 @@
+package guestmetric
+
+import (
+ "testing"
+)
+
+func TestCollector(t *testing.T) {
+ c := Collector{
+ Client: nil,
+ }
+
+ funcs := []CollectFunc{
+ c.CollectDisk,
+ c.CollectMemory,
+ c.CollectMisc,
+ c.CollectNetworkAddr,
+ }
+
+ for _, f := range funcs {
+ metric, err := f()
+ if err != nil {
+ t.Errorf("%#v error: %#v\n", f, err)
+ }
+ t.Logf("%#v return %#v", f, metric)
+ }
+}
+
+func doBenchmark(b *testing.B, f CollectFunc) {
+ b.Logf("doBenchmark 1000 for %#v", f)
+ for i := 0; i < 1000; i++ {
+ if _, err := f(); err != nil {
+ b.Errorf("%#v error: %#v\n", f, err)
+ }
+ }
+}
+
+func BenchmarkCollectorDisk(b *testing.B) {
+ c := Collector{
+ Client: nil,
+ }
+ doBenchmark(b, c.CollectDisk)
+}
+
+func BenchmarkCollectMemory(b *testing.B) {
+ c := Collector{
+ Client: nil,
+ }
+ doBenchmark(b, c.CollectMemory)
+}
+
+func BenchmarkCollectMisc(b *testing.B) {
+ c := Collector{
+ Client: nil,
+ }
+ doBenchmark(b, c.CollectMisc)
+}
+
+func BenchmarkCollectNetwork(b *testing.B) {
+ c := Collector{
+ Client: nil,
+ }
+ doBenchmark(b, c.CollectNetworkAddr)
+}