├── CHANGELOG.md ├── README.md ├── client └── client.go ├── config └── env.go ├── cpu ├── cpu.go └── cpu_test.go ├── docker ├── client.go ├── container_registration.go ├── id_cache.go ├── list.go ├── listen.go ├── path.go └── pid.go ├── mem ├── mem.go └── mem_test.go ├── net └── net.go ├── runner ├── net │ └── stats_runner.go └── runner.go └── server └── acadock_monitoring.go /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v0.1.2 4 | 5 | * CPU monitoring "not ready" -> -1 6 | 7 | ## v0.1.1 8 | 9 | * Parse memory on 64bits 10 | 11 | ## v0.1.0 12 | 13 | * Metrics with Docker - Systemd 14 | * Metrics with Docker - Libcontainer 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Acadock Monitoring - Docker container monitoring 2 | ================================================ 3 | 4 | This webservice provides live data on Docker containers. It takes 5 | data from the Linux kernel control groups and from the namespace of 6 | the container and expose them through a HTTP API. 7 | 8 | > The solution is still a work in progress. 9 | 10 | Configuration 11 | ------------- 12 | 13 | From environment 14 | 15 | * `PORT`: port to bind (4244 by default) 16 | 17 | API 18 | --- 19 | 20 | * Memory consumption (in bytes) 21 | 22 | `GET /containers/:id/mem` 23 | 24 | Return 200 OK 25 | Content-Type: text/plain 26 | 27 | * CPU usage (percentage) 28 | 29 | Return 200 OK 30 | Content-Type: text/plain 31 | `GET /containers/:id/cpu` 32 | 33 | * Network usage (bytes and percentage) 34 | 35 | Return 200 OK 36 | Content-Type: application/json 37 | `GET /containers/:id/net` 38 | 39 | ### Developers 40 | 41 | > Léo Unbekandt `` 42 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/url" 7 | "strconv" 8 | 9 | "gopkg.in/errgo.v1" 10 | ) 11 | 12 | type Client struct { 13 | Endpoint string 14 | } 15 | 16 | func NewClient(endpoint string) (*Client, error) { 17 | _, err := url.Parse(endpoint) 18 | if err != nil { 19 | return nil, errgo.Mask(err) 20 | } 21 | 22 | c := &Client{Endpoint: endpoint} 23 | return c, nil 24 | } 25 | 26 | func (c *Client) Memory(dockerId string) (int64, error) { 27 | req, err := http.NewRequest("GET", c.Endpoint+"/containers/"+dockerId+"/mem", nil) 28 | if err != nil { 29 | return -1, errgo.Mask(err) 30 | } 31 | 32 | mem, err := c.getInt(req) 33 | if err != nil { 34 | return -1, errgo.Mask(err) 35 | } 36 | 37 | return mem, nil 38 | } 39 | 40 | func (c *Client) CpuUsage(dockerId string) (int64, error) { 41 | req, err := http.NewRequest("GET", c.Endpoint+"/containers/"+dockerId+"/cpu", nil) 42 | if err != nil { 43 | return -1, errgo.Mask(err) 44 | } 45 | 46 | cpu, err := c.getInt(req) 47 | if err != nil { 48 | return -1, errgo.Mask(err) 49 | } 50 | 51 | return cpu, nil 52 | } 53 | 54 | func (c *Client) do(req *http.Request) (*http.Response, error) { 55 | req.Header.Add("Content-Type", "text/plain") 56 | req.Header.Add("User-Agent", "Acadocker Client v1") 57 | return http.DefaultClient.Do(req) 58 | } 59 | 60 | func (c *Client) getInt(req *http.Request) (int64, error) { 61 | res, err := c.do(req) 62 | if err != nil { 63 | return -1, errgo.Mask(err) 64 | } 65 | defer res.Body.Close() 66 | 67 | body, err := ioutil.ReadAll(res.Body) 68 | if err != nil { 69 | return -1, errgo.Mask(err) 70 | } 71 | 72 | i, err := strconv.ParseInt(string(body), 10, 64) 73 | if err != nil { 74 | return -1, errgo.Mask(err) 75 | } 76 | 77 | return i, nil 78 | } 79 | -------------------------------------------------------------------------------- /config/env.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | ) 7 | 8 | var ENV = map[string]string{ 9 | "DOCKER_URL": "http://localhost:4243", 10 | "PORT": "4244", 11 | "REFRESH_TIME": "5", 12 | "CGROUP_SOURCE": "docker", 13 | } 14 | 15 | var RefreshTime int 16 | 17 | func init() { 18 | for k, v := range ENV { 19 | if os.Getenv(k) != "" { 20 | ENV[k] = os.Getenv(k) 21 | } else { 22 | os.Setenv(k, v) 23 | } 24 | } 25 | 26 | var err error 27 | RefreshTime, err = strconv.Atoi(ENV["REFRESH_TIME"]) 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | func CgroupPath(cgroup string, id string) string { 34 | if ENV["CGROUP_SOURCE"] == "docker" { 35 | return "/sys/fs/cgroup/" + cgroup + "/docker/" + id 36 | } else if ENV["CGROUP_SOURCE"] == "systemd" { 37 | return "/sys/fs/cgroup/" + cgroup + "/system.slice/docker-" + id + ".scope" 38 | } else { 39 | panic("unknown cgroup source" + ENV["CGROUP_SOURCE"]) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cpu/cpu.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "os" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | "github.com/Soulou/acadock-monitoring/config" 16 | "github.com/Soulou/acadock-monitoring/docker" 17 | ) 18 | 19 | const ( 20 | LXC_CPUACCT_USAGE_FILE = "cpuacct.usage" 21 | ) 22 | 23 | var ( 24 | currentSystemUsage = make(map[string]int64) 25 | previousSystemUsage = make(map[string]int64) 26 | previousCpuUsages = make(map[string]int64) 27 | cpuUsages = make(map[string]int64) 28 | cpuUsagesMutex = &sync.Mutex{} 29 | ) 30 | 31 | func cpuacctUsage(container string) (int64, error) { 32 | file := config.CgroupPath("cpuacct", container) + "/" + LXC_CPUACCT_USAGE_FILE 33 | f, err := os.Open(file) 34 | if err != nil { 35 | log.Println(err) 36 | return 0, err 37 | } 38 | 39 | buffer := make([]byte, 16) 40 | n, err := f.Read(buffer) 41 | buffer = buffer[:n] 42 | 43 | bufferStr := string(buffer) 44 | bufferStr = bufferStr[:len(bufferStr)-1] 45 | 46 | res, err := strconv.ParseInt(bufferStr, 10, 64) 47 | if err != nil { 48 | log.Println("Failed to parse : ", err) 49 | return 0, err 50 | } 51 | return res, nil 52 | } 53 | 54 | func Monitor() { 55 | containers := docker.RegisterToContainersStream() 56 | for c := range containers { 57 | fmt.Println("monitor cpu", c) 58 | go monitorContainer(c) 59 | } 60 | } 61 | 62 | func monitorContainer(id string) { 63 | tick := time.NewTicker(time.Duration(config.RefreshTime) * time.Second) 64 | for { 65 | <-tick.C 66 | var err error 67 | usage, err := cpuacctUsage(id) 68 | if err != nil { 69 | if _, ok := cpuUsages[id]; ok { 70 | delete(cpuUsages, id) 71 | } 72 | if _, ok := previousCpuUsages[id]; ok { 73 | delete(previousCpuUsages, id) 74 | } 75 | log.Println("stop monitoring", id, "reason:", err) 76 | return 77 | } 78 | 79 | cpuUsagesMutex.Lock() 80 | previousCpuUsages[id] = cpuUsages[id] 81 | cpuUsages[id] = usage 82 | cpuUsagesMutex.Unlock() 83 | 84 | previousSystemUsage[id] = currentSystemUsage[id] 85 | currentSystemUsage[id], err = systemUsage() 86 | if err != nil { 87 | log.Println(err) 88 | } 89 | } 90 | } 91 | 92 | func GetUsage(id string) (int64, error) { 93 | id, err := docker.ExpandId(id) 94 | if err != nil { 95 | log.Println("Error when expanding id:", err) 96 | return -1, err 97 | } 98 | if _, ok := previousCpuUsages[id]; !ok { 99 | return -1, nil 100 | } 101 | deltaCpuUsage := float64(cpuUsages[id] - previousCpuUsages[id]) 102 | deltaSystemCpuUsage := float64(currentSystemUsage[id] - previousSystemUsage[id]) 103 | 104 | return int64(deltaCpuUsage / deltaSystemCpuUsage * 100 * float64(runtime.NumCPU())), nil 105 | } 106 | 107 | func systemUsage() (int64, error) { 108 | f, err := os.OpenFile("/proc/stat", os.O_RDONLY, 0600) 109 | if err != nil { 110 | return -1, err 111 | } 112 | 113 | var line string 114 | buffer := bufio.NewReader(f) 115 | for { 116 | lineBytes, _, err := buffer.ReadLine() 117 | if err != nil { 118 | return -1, err 119 | } 120 | line = string(lineBytes) 121 | if strings.HasPrefix(line, "cpu ") { 122 | break 123 | } 124 | } 125 | 126 | err = f.Close() 127 | if err != nil { 128 | return -1, err 129 | } 130 | 131 | fields := strings.Fields(string(line)) 132 | if len(fields) < 8 { 133 | return -1, errors.New("invalid cpu line in /stat/proc: " + string(line)) 134 | } 135 | 136 | sum := int64(0) 137 | for i := 1; i < 8; i++ { 138 | n, err := strconv.ParseInt(fields[i], 10, 64) 139 | if err != nil { 140 | return -1, err 141 | } 142 | sum += n 143 | } 144 | 145 | return sum * 1e9 / 100, nil 146 | } 147 | -------------------------------------------------------------------------------- /cpu/cpu_test.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/Soulou/acadock-live-lxc/lxc" 8 | ) 9 | 10 | const ( 11 | LXC_START_COMMAND = "lxc-start" 12 | ) 13 | 14 | func TestGetUsage(t *testing.T) { 15 | // Need Root permission 16 | /* err := exec.Command(LXC_START_COMMAND, "-d", "--name", "test_cpu_container", "/usr/bin/sleep 10").Run() */ 17 | /* if err != nil { */ 18 | /* t.Fatal("Please install", LXC_START_COMMAND, err) */ 19 | /* } */ 20 | 21 | containers, err := lxc.ListContainers() 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if len(containers) == 0 { 26 | t.Log("There isn't any existing container, please create one") 27 | t.Log("Example : ") 28 | t.Log("\t# lxc-start -d --name test_cpu_container /usr/bin/sleep 10") 29 | t.FailNow() 30 | } 31 | 32 | go Monitor() 33 | 34 | time.Sleep(2 * time.Second) 35 | 36 | cpuUsage := GetUsage(containers[0]) 37 | if cpuUsage < 0 || cpuUsage > 100 { 38 | t.Fatal("CPU usage out of [0,100]") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docker/client.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/Soulou/acadock-monitoring/config" 5 | "github.com/fsouza/go-dockerclient" 6 | "gopkg.in/errgo.v1" 7 | ) 8 | 9 | func Client() (*docker.Client, error) { 10 | client, err := docker.NewClient(config.ENV["DOCKER_URL"]) 11 | if err != nil { 12 | return nil, errgo.Mask(err) 13 | } 14 | return client, nil 15 | } 16 | -------------------------------------------------------------------------------- /docker/container_registration.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import "sync" 4 | 5 | var ( 6 | registeredChans []chan string 7 | registrationMutex *sync.Mutex 8 | ) 9 | 10 | func init() { 11 | registrationMutex = &sync.Mutex{} 12 | containers := make(chan string) 13 | go ListenNewContainers(containers) 14 | go func() { 15 | for c := range containers { 16 | registrationMutex.Lock() 17 | for _, registeredChan := range registeredChans { 18 | registeredChan <- c 19 | } 20 | registrationMutex.Unlock() 21 | } 22 | }() 23 | } 24 | 25 | func RegisterToContainersStream() chan string { 26 | c := make(chan string, 1) 27 | registrationMutex.Lock() 28 | defer registrationMutex.Unlock() 29 | registeredChans = append(registeredChans, c) 30 | go ListRunningContainers(c) 31 | return c 32 | } 33 | -------------------------------------------------------------------------------- /docker/id_cache.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var IdNotInCache = errors.New("id not found in cache") 9 | 10 | var containerIdsCache = []string{} 11 | 12 | func expandIdFromCache(id string) (string, error) { 13 | for _, fullId := range containerIdsCache { 14 | if strings.HasPrefix(fullId, id) { 15 | return fullId, nil 16 | } 17 | } 18 | return "", IdNotInCache 19 | } 20 | -------------------------------------------------------------------------------- /docker/list.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/fsouza/go-dockerclient" 5 | "gopkg.in/errgo.v1" 6 | ) 7 | 8 | func ListRunningContainers(ids chan string) error { 9 | client, err := Client() 10 | if err != nil { 11 | return errgo.Mask(err) 12 | } 13 | 14 | containers, err := client.ListContainers(docker.ListContainersOptions{}) 15 | if err != nil { 16 | return errgo.Mask(err) 17 | } 18 | 19 | for _, container := range containers { 20 | ids <- container.ID 21 | } 22 | 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /docker/listen.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/fsouza/go-dockerclient" 5 | "gopkg.in/errgo.v1" 6 | ) 7 | 8 | func ListenNewContainers(ids chan string) error { 9 | client, err := Client() 10 | if err != nil { 11 | return errgo.Mask(err) 12 | } 13 | 14 | listener := make(chan *docker.APIEvents) 15 | err = client.AddEventListener(listener) 16 | if err != nil { 17 | return errgo.Mask(err) 18 | } 19 | 20 | for event := range listener { 21 | if event.Status == "start" { 22 | ids <- event.ID 23 | } 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /docker/path.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/Soulou/acadock-monitoring/config" 9 | "gopkg.in/errgo.v1" 10 | ) 11 | 12 | func ExpandId(id string) (string, error) { 13 | fullId, err := expandIdFromCache(id) 14 | if err == nil { 15 | return fullId, nil 16 | } 17 | if err != IdNotInCache { 18 | return "", errgo.Mask(err) 19 | } 20 | 21 | dir := filepath.Dir(config.CgroupPath("memory", id)) 22 | switch config.ENV["CGROUP_SOURCE"] { 23 | case "systemd": 24 | id, err = expandSystemdId(dir, id) 25 | case "docker": 26 | id, err = expandDockerId(dir, id) 27 | default: 28 | panic("not a known CGROUP SOURCE") 29 | } 30 | if err != nil { 31 | return "", errgo.Mask(err) 32 | } 33 | 34 | containerIdsCache = append(containerIdsCache, id) 35 | return id, nil 36 | } 37 | 38 | func expandSystemdId(dir, id string) (string, error) { 39 | d, err := os.Open(dir) 40 | if err != nil { 41 | return "", errgo.Mask(err) 42 | } 43 | files, err := d.Readdirnames(0) 44 | if err != nil { 45 | return "", errgo.Mask(err) 46 | } 47 | for _, f := range files { 48 | if len(f) < 32 { 49 | continue 50 | } 51 | fullId := f[7 : len(f)-6] 52 | if strings.HasPrefix(fullId, id) { 53 | return fullId, nil 54 | } 55 | } 56 | return "", errgo.New("id not found") 57 | } 58 | 59 | func expandDockerId(dir, id string) (string, error) { 60 | d, err := os.Open(dir) 61 | if err != nil { 62 | return "", errgo.Mask(err) 63 | } 64 | files, err := d.Readdirnames(0) 65 | if err != nil { 66 | return "", errgo.Mask(err) 67 | } 68 | for _, f := range files { 69 | if strings.HasPrefix(f, id) { 70 | return f, nil 71 | } 72 | } 73 | return "", errgo.New("id not found") 74 | } 75 | -------------------------------------------------------------------------------- /docker/pid.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "time" 7 | 8 | "github.com/Soulou/acadock-monitoring/config" 9 | ) 10 | 11 | func Pid(id string) (string, error) { 12 | time.Sleep(time.Second) 13 | path := config.CgroupPath("memory", id) 14 | content, err := ioutil.ReadFile(path + "/tasks") 15 | if err != nil { 16 | return "", err 17 | } 18 | return strings.Split(string(content), "\n")[0], nil 19 | } 20 | -------------------------------------------------------------------------------- /mem/mem.go: -------------------------------------------------------------------------------- 1 | package mem 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/Soulou/acadock-monitoring/config" 9 | "github.com/Soulou/acadock-monitoring/docker" 10 | ) 11 | 12 | const ( 13 | LXC_MEM_USAGE_FILE = "memory.usage_in_bytes" 14 | ) 15 | 16 | func GetUsage(id string) (int64, error) { 17 | id, err := docker.ExpandId(id) 18 | if err != nil { 19 | log.Println("Error when expanding id:", err) 20 | return 0, err 21 | } 22 | 23 | path := config.CgroupPath("memory", id) + "/" + LXC_MEM_USAGE_FILE 24 | f, err := os.Open(path) 25 | if err != nil { 26 | log.Println("Error while opening:", err) 27 | return 0, err 28 | } 29 | defer f.Close() 30 | 31 | buffer := make([]byte, 16) 32 | n, err := f.Read(buffer) 33 | if err != nil { 34 | log.Println("Error while reading ", path, ":", err) 35 | return 0, err 36 | } 37 | 38 | buffer = buffer[:n-1] 39 | val, err := strconv.ParseInt(string(buffer), 10, 64) 40 | if err != nil { 41 | log.Println("Error while parsing ", string(buffer), " : ", err) 42 | return 0, err 43 | } 44 | 45 | return val, nil 46 | } 47 | -------------------------------------------------------------------------------- /mem/mem_test.go: -------------------------------------------------------------------------------- 1 | package mem 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Soulou/acadock-live-lxc/lxc" 7 | ) 8 | 9 | const ( 10 | LXC_START_COMMAND = "lxc-start" 11 | ) 12 | 13 | func TestGetUsage(t *testing.T) { 14 | // Need Root permission 15 | /* err := exec.Command(LXC_START_COMMAND, "-d", "--name", "test_cpu_container", "/usr/bin/sleep 10").Run() */ 16 | /* if err != nil { */ 17 | /* t.Fatal("Please install", LXC_START_COMMAND, err) */ 18 | /* } */ 19 | 20 | containers, err := lxc.ListContainers() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | if len(containers) == 0 { 25 | t.Log("There isn't any existing container, please create one") 26 | t.Log("Example : ") 27 | t.Log("\t# lxc-start -d --name test_cpu_container /usr/bin/sleep 10") 28 | t.FailNow() 29 | } 30 | 31 | memUsage, err := GetUsage(containers[0]) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | if memUsage < 0 || memUsage > 100 { 37 | t.Fatal("CPU usage out of [0,100]") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /net/net.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "sync" 8 | "time" 9 | 10 | "github.com/Scalingo/go-netstat" 11 | "github.com/Soulou/acadock-monitoring/config" 12 | "github.com/Soulou/acadock-monitoring/docker" 13 | "github.com/Soulou/acadock-monitoring/runner" 14 | ) 15 | 16 | var ( 17 | netUsages = make(map[string]netstat.NetworkStat) 18 | previousNetUsages = make(map[string]netstat.NetworkStat) 19 | netUsagesMutex = &sync.Mutex{} 20 | nsMutex = &sync.Mutex{} 21 | ) 22 | 23 | func Monitor(iface string) { 24 | containers := docker.RegisterToContainersStream() 25 | for c := range containers { 26 | fmt.Println("monitor", iface, "of", c) 27 | go monitorContainer(c, iface) 28 | } 29 | } 30 | 31 | func monitorContainer(id string, iface string) { 32 | pid, err := docker.Pid(id) 33 | if err != nil { 34 | log.Println("Fail to get PID of", id, ":", err) 35 | return 36 | } 37 | 38 | tick := time.NewTicker(time.Duration(config.RefreshTime) * time.Second) 39 | for { 40 | <-tick.C 41 | stats, err := runner.NetStatsRunner(pid) 42 | if err != nil { 43 | if os.IsNotExist(err) { 44 | stopMonitoring(id) 45 | } else { 46 | log.Println("Error fetching stats of", id, ":", err) 47 | } 48 | return 49 | } 50 | for _, stat := range stats { 51 | if stat.Interface == iface { 52 | netUsagesMutex.Lock() 53 | previousNetUsages[id] = netUsages[id] 54 | netUsages[id] = stat 55 | netUsagesMutex.Unlock() 56 | break 57 | } 58 | } 59 | } 60 | } 61 | 62 | type NetUsage struct { 63 | netstat.NetworkStat 64 | RxBps int64 65 | TxBps int64 66 | } 67 | 68 | func GetUsage(id string) (*NetUsage, error) { 69 | id, err := docker.ExpandId(id) 70 | if err != nil { 71 | return nil, err 72 | } 73 | usage := NetUsage{} 74 | usage.NetworkStat = netUsages[id] 75 | usage.RxBps = int64(float64(netUsages[id].Received.Bytes-previousNetUsages[id].Received.Bytes) / float64(config.RefreshTime)) 76 | usage.TxBps = int64(float64(netUsages[id].Transmit.Bytes-previousNetUsages[id].Transmit.Bytes) / float64(config.RefreshTime)) 77 | return &usage, nil 78 | } 79 | 80 | func stopMonitoring(id string) { 81 | log.Println("End of network monitoring for", id) 82 | netUsagesMutex.Lock() 83 | delete(previousNetUsages, id) 84 | delete(netUsages, id) 85 | netUsagesMutex.Unlock() 86 | } 87 | -------------------------------------------------------------------------------- /runner/net/stats_runner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | 8 | "github.com/Scalingo/go-netstat" 9 | ) 10 | 11 | func main() { 12 | stats, err := netstat.Stats() 13 | if err != nil { 14 | log.Println("Error when getting network stats", err) 15 | os.Exit(-1) 16 | } 17 | json.NewEncoder(os.Stdout).Encode(&stats) 18 | } 19 | -------------------------------------------------------------------------------- /runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "os/exec" 7 | 8 | "github.com/Scalingo/go-netns" 9 | "github.com/Scalingo/go-netstat" 10 | ) 11 | 12 | func NetStatsRunner(pid string) (netstat.NetworkStats, error) { 13 | ns, err := netns.Setns(pid) 14 | if err != nil { 15 | return nil, err 16 | } 17 | defer ns.Close() 18 | 19 | path, err := exec.LookPath("net_stats_runner") 20 | if err != nil { 21 | return nil, err 22 | } 23 | stdout := new(bytes.Buffer) 24 | cmd := exec.Command(path) 25 | cmd.Stdout = stdout 26 | err = cmd.Start() 27 | if err != nil { 28 | return nil, err 29 | } 30 | err = cmd.Wait() 31 | if err != nil { 32 | return nil, err 33 | } 34 | var stats netstat.NetworkStats 35 | err = json.NewDecoder(stdout).Decode(&stats) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return stats, nil 40 | } 41 | -------------------------------------------------------------------------------- /server/acadock_monitoring.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "log" 7 | "strconv" 8 | 9 | "net/http" 10 | "net/http/pprof" 11 | 12 | "github.com/Soulou/acadock-monitoring/cpu" 13 | "github.com/Soulou/acadock-monitoring/mem" 14 | "github.com/Soulou/acadock-monitoring/net" 15 | "github.com/codegangsta/martini" 16 | ) 17 | 18 | func containerMemUsageHandler(params martini.Params) (int, string) { 19 | id := params["id"] 20 | 21 | containerMemory, err := mem.GetUsage(id) 22 | if err != nil { 23 | return 500, err.Error() 24 | } 25 | containerMemoryStr := strconv.FormatInt(containerMemory, 10) 26 | return 200, containerMemoryStr 27 | } 28 | 29 | func containerCpuUsageHandler(params martini.Params) (int, string) { 30 | id := params["id"] 31 | 32 | containerCpu, err := cpu.GetUsage(id) 33 | if err != nil { 34 | return 200, err.Error() 35 | } 36 | containerCpuStr := strconv.FormatInt(containerCpu, 10) 37 | return 200, containerCpuStr 38 | } 39 | 40 | func containerNetUsageHandler(params martini.Params, res http.ResponseWriter) { 41 | id := params["id"] 42 | 43 | containerNet, err := net.GetUsage(id) 44 | if err != nil { 45 | res.Write([]byte(err.Error())) 46 | return 47 | } 48 | 49 | res.WriteHeader(200) 50 | json.NewEncoder(res).Encode(&containerNet) 51 | } 52 | 53 | func main() { 54 | doProfile := flag.Bool("profile", false, "profile app") 55 | flag.Parse() 56 | go cpu.Monitor() 57 | go net.Monitor("eth0") 58 | 59 | r := martini.Classic() 60 | 61 | r.Get("/containers/:id/mem", containerMemUsageHandler) 62 | r.Get("/containers/:id/cpu", containerCpuUsageHandler) 63 | r.Get("/containers/:id/net", containerNetUsageHandler) 64 | 65 | if *doProfile { 66 | log.Println("Enable profiling") 67 | r.Get("/debug/pprof", pprof.Index) 68 | r.Get("/debug/pprof/cmdline", pprof.Cmdline) 69 | r.Get("/debug/pprof/profile", pprof.Profile) 70 | r.Get("/debug/pprof/symbol", pprof.Symbol) 71 | r.Post("/debug/pprof/symbol", pprof.Symbol) 72 | r.Get("/debug/pprof/block", pprof.Handler("block").ServeHTTP) 73 | r.Get("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP) 74 | r.Get("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP) 75 | r.Get("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) 76 | } 77 | r.Run() 78 | } 79 | --------------------------------------------------------------------------------