Created
February 26, 2020 10:39
-
-
Save maurorappa/47c917f945a9cb25b047e097c2b98f30 to your computer and use it in GitHub Desktop.
docker-stats: get memory stats from cgroups
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"flag" | |
"fmt" | |
"github.com/docker/docker/api/types" | |
"github.com/docker/docker/api/types/filters" | |
"github.com/docker/docker/client" | |
//"github.com/guptarohit/asciigraph" | |
"io/ioutil" | |
"log" | |
"os" | |
"sort" | |
"strconv" | |
"strings" | |
"time" | |
) | |
const cgroupPath = "/sys/fs/cgroup/memory/docker/" | |
const logPath = "/tmp" | |
var ( | |
stats = map[string]int{} | |
runningContainers = map[string]int{} | |
deadContainers = map[string]int{} | |
verbose bool | |
ancestor string | |
) | |
func main() { | |
interval := flag.Int("i", 5, "update interval") | |
flag.StringVar(&ancestor, "a", "", "coma separated image to filter") | |
flag.BoolVar(&verbose, "v", false, "verbose") | |
//saveStatFile := flag.Bool("s",true, "save container stats in /tmp") | |
flag.Parse() | |
if verbose { | |
log.Printf("\nScanning Docker containers every %d seconds\n", *interval) | |
} | |
if *interval > 0 { | |
//set a x seconds ticker | |
ticker := time.NewTicker(time.Duration(*interval) * time.Second) | |
go func() { | |
for _ = range ticker.C { | |
dockerStats() | |
//generateGraphs() | |
} | |
}() | |
} | |
select {} | |
} | |
func dockerStats() { | |
cli, err := client.NewClientWithOpts(client.FromEnv) | |
if err != nil { | |
panic(err) | |
} | |
filters := filters.NewArgs() | |
filters.Add("status","running") | |
if ancestor != "" { | |
images := strings.Split(string(ancestor), ",") | |
if len(images) == 0 && verbose { | |
fmt.Printf("no images specified! separate by a comma, please\n") | |
} | |
for _, img := range images { | |
filters.Add("ancestor", img) | |
} | |
} | |
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{ | |
Size: true, | |
All: false, | |
Since: "container", | |
Filters: filters, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
cli.Close() | |
if len(containers)==0 && verbose { | |
fmt.Printf("no container based on the requested image\n") | |
} | |
for _, container := range containers { | |
//fmt.Printf("%s %s\n", container.ID, container.Image) | |
if _, fresh := runningContainers[container.ID]; !fresh { | |
runningContainers[container.ID] = 1 | |
memContainer(container.ID, true) | |
} else { | |
memContainer(container.ID, false) | |
} | |
} | |
var found bool | |
//find stopped containers | |
for id, _ := range runningContainers { | |
found = false | |
if verbose { fmt.Printf("checking %s\n", id[:10]) } | |
for _, containerID := range containers { | |
if id == containerID.ID { | |
found = true | |
break | |
} | |
} | |
if !found { | |
fmt.Printf("dead!\n") | |
delete(runningContainers, id) | |
} | |
} | |
} | |
func memContainer(id string, header bool) { | |
if verbose { | |
log.Printf("\n\nStats for %s", id) | |
} | |
content, err := ioutil.ReadFile(cgroupPath + id + "/memory.stat") | |
// see https://crate.io/a/analyzing-docker-container-performance-native-tools | |
if err != nil { | |
//Do something | |
} | |
lines := strings.Split(string(content), "\n") | |
keys := make([]string, 0, 20) | |
// fmt.Printf("%q\n", lines) | |
for _, line := range lines { | |
info := strings.Split(string(line), " ") | |
// check for useful lines | |
if len(info) < 2 { | |
continue | |
} | |
// we only care about total container stats | |
if len(info[0]) > 7 && info[0][:6] == "total_" { | |
num, _ := strconv.Atoi(info[1]) | |
stats[info[0][6:]] = num | |
keys = append(keys, info[0][6:]) | |
} | |
} | |
sort.Strings(keys) | |
filename := logPath + "/" + id[:10] + ".csv" | |
csv, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) | |
now := time.Now().Unix() | |
defer csv.Close() | |
if err != nil { | |
panic(err) | |
} | |
allInfo := "" | |
line := "" | |
if header { | |
head := "time," | |
for _, k := range keys { | |
line = fmt.Sprintf("%s,", k) | |
head = head + line | |
} | |
head = head + "\n" | |
_, err = csv.WriteString(head) | |
if err != nil { | |
panic(err) | |
} | |
//fmt.Println(head+"\n") | |
} | |
allInfo = strconv.FormatInt(now, 10) + "," | |
for _, k := range keys { | |
//line := fmt.Sprintf("%s: %d\n",k,stats[k]) | |
line = fmt.Sprintf("%d,", stats[k]) | |
allInfo = allInfo + line | |
} | |
allInfo = allInfo + "\n" | |
if verbose { fmt.Println(allInfo) } | |
_, err = csv.WriteString(allInfo) | |
if err != nil { | |
panic(err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment