| package task |
|
|
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "log" |
| "net" |
| "net/http" |
| "net/url" |
| "time" |
|
|
| dockerTypes "github.com/docker/docker/api/types" |
| units "github.com/docker/go-units" |
| lru "github.com/hashicorp/golang-lru" |
| "github.com/play-with-docker/play-with-docker/docker" |
| "github.com/play-with-docker/play-with-docker/event" |
| "github.com/play-with-docker/play-with-docker/pwd/types" |
| "github.com/play-with-docker/play-with-docker/router" |
| "github.com/play-with-docker/play-with-docker/storage" |
| ) |
|
|
| type InstanceStats struct { |
| Instance string `json:"instance"` |
| Mem string `json:"mem"` |
| Cpu string `json:"cpu"` |
| } |
|
|
| type collectStats struct { |
| event event.EventApi |
| factory docker.FactoryApi |
| cli *http.Client |
| cache *lru.Cache |
| storage storage.StorageApi |
| } |
|
|
| var CollectStatsEvent event.EventType |
|
|
| func init() { |
| CollectStatsEvent = event.EventType("instance stats") |
| } |
|
|
| func (t *collectStats) Name() string { |
| return "CollectStats" |
| } |
|
|
| func (t *collectStats) Run(ctx context.Context, instance *types.Instance) error { |
| if instance.Type == "windows" { |
| host := router.EncodeHost(instance.SessionId, instance.IP, router.HostOpts{EncodedPort: 222}) |
| req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/stats", host), nil) |
| if err != nil { |
| log.Printf("Could not create request to get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) |
| return fmt.Errorf("Could not create request to get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) |
| } |
| req.Header.Set("X-Proxy-Host", instance.SessionHost) |
| resp, err := t.cli.Do(req) |
| if err != nil { |
| log.Printf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) |
| return fmt.Errorf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) |
| } |
| if resp.StatusCode != 200 { |
| log.Printf("Could not get stats of windows instance with IP %s. Got status code: %d\n", instance.IP, resp.StatusCode) |
| return fmt.Errorf("Could not get stats of windows instance with IP %s. Got status code: %d\n", instance.IP, resp.StatusCode) |
| } |
| var info map[string]float64 |
| err = json.NewDecoder(resp.Body).Decode(&info) |
| if err != nil { |
| log.Printf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) |
| return fmt.Errorf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) |
| } |
| stats := InstanceStats{Instance: instance.Name} |
|
|
| stats.Mem = fmt.Sprintf("%.2f%% (%s / %s)", ((info["mem_used"] / info["mem_total"]) * 100), units.BytesSize(info["mem_used"]), units.BytesSize(info["mem_total"])) |
| stats.Cpu = fmt.Sprintf("%.2f%%", info["cpu"]*100) |
| t.event.Emit(CollectStatsEvent, instance.SessionId, stats) |
| return nil |
| } |
| var session *types.Session |
| if sess, found := t.cache.Get(instance.SessionId); !found { |
| s, err := t.storage.SessionGet(instance.SessionId) |
| if err != nil { |
| return err |
| } |
| t.cache.Add(s.Id, s) |
| session = s |
| } else { |
| session = sess.(*types.Session) |
| } |
| dockerClient, err := t.factory.GetForSession(session) |
| if err != nil { |
| log.Println(err) |
| return err |
| } |
| reader, err := dockerClient.ContainerStats(instance.Name) |
| if err != nil { |
| log.Println("Error while trying to collect instance stats", err) |
| return err |
| } |
| dec := json.NewDecoder(reader) |
| var v *dockerTypes.StatsJSON |
| e := dec.Decode(&v) |
| if e != nil { |
| log.Println("Error while trying to collect instance stats", e) |
| return err |
| } |
| stats := InstanceStats{Instance: instance.Name} |
| |
| var memPercent float64 = 0 |
| if v.MemoryStats.Limit != 0 { |
| memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 |
| } |
| mem := float64(v.MemoryStats.Usage) |
| memLimit := float64(v.MemoryStats.Limit) |
|
|
| stats.Mem = fmt.Sprintf("%.2f%% (%s / %s)", memPercent, units.BytesSize(mem), units.BytesSize(memLimit)) |
|
|
| |
| previousCPU := v.PreCPUStats.CPUUsage.TotalUsage |
| previousSystem := v.PreCPUStats.SystemUsage |
| cpuPercent := calculateCPUPercentUnix(previousCPU, previousSystem, v) |
| stats.Cpu = fmt.Sprintf("%.2f%%", cpuPercent) |
|
|
| t.event.Emit(CollectStatsEvent, instance.SessionId, stats) |
| return nil |
| } |
|
|
| func proxyHost(r *http.Request) (*url.URL, error) { |
| if r.Header.Get("X-Proxy-Host") == "" { |
| return nil, nil |
| } |
| u := new(url.URL) |
| *u = *r.URL |
| u.Host = fmt.Sprintf("%s:8443", r.Header.Get("X-Proxy-Host")) |
| return u, nil |
| } |
|
|
| func NewCollectStats(e event.EventApi, f docker.FactoryApi, s storage.StorageApi) *collectStats { |
| transport := &http.Transport{ |
| DialContext: (&net.Dialer{ |
| Timeout: 1 * time.Second, |
| KeepAlive: 30 * time.Second, |
| }).DialContext, |
| MaxIdleConnsPerHost: 5, |
| Proxy: proxyHost, |
| } |
| cli := &http.Client{ |
| Transport: transport, |
| } |
| c, _ := lru.New(5000) |
| return &collectStats{event: e, factory: f, cli: cli, cache: c, storage: s} |
| } |
|
|
| func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *dockerTypes.StatsJSON) float64 { |
| var ( |
| cpuPercent = 0.0 |
| |
| cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) |
| |
| systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) |
| ) |
|
|
| if systemDelta > 0.0 && cpuDelta > 0.0 { |
| cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 |
| } |
| return cpuPercent |
| } |
|
|