func (m DockerPlugin) FetchMetricsWithAPI(containers []docker.APIContainers) (map[string]interface{}, error) {
	var wg sync.WaitGroup
	var mu sync.Mutex
	res := map[string]interface{}{}
	for _, container := range containers {
		wg.Add(1)
		go func(cont docker.APIContainers) {
			defer wg.Done()
			name := strings.Replace(cont.Names[0], "/", "", 1)
			metricName := normalizeMetricName(m.generateName(cont))
			client, _ := docker.NewClient(m.Host)
			errC := make(chan error, 1)
			statsC := make(chan *docker.Stats)
			done := make(chan bool)
			go func() {
				errC <- client.Stats(docker.StatsOptions{ID: name, Stats: statsC, Stream: false, Done: done, Timeout: time.Duration(20) * time.Second})
				close(errC)
			}()
			var resultStats []*docker.Stats
			for {
				stats, ok := <-statsC
				if !ok {
					break
				}
				resultStats = append(resultStats, stats)
			}
			err := <-errC
			if err != nil {
				log.Fatal(err)
			}
			if len(resultStats) == 0 {
				log.Fatalf("Stats: Expected 1 result. Got %d.", len(resultStats))
			}
			mu.Lock()
			m.parseStats(&res, metricName, resultStats[0])
			mu.Unlock()
		}(container)
	}
	wg.Wait()
	return res, nil
}
FetchMetricsWithAPI use docker API to fetch metrics
package mpdocker
import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/fsouza/go-dockerclient"
	mp "github.com/mackerelio/go-mackerel-plugin-helper"
)
var graphdef = map[string]mp.Graphs{
	"docker.cpuacct.#": {
		Label: "Docker CPU",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "user", Label: "User", Diff: true, Stacked: true, Type: "uint64"},
			{Name: "system", Label: "System", Diff: true, Stacked: true, Type: "uint64"},
		},
	},
	"docker.cpuacct_percentage.#": {
		Label: "Docker CPU Percentage",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "user", Label: "User", Diff: false, Stacked: true, Type: "float64"},
			{Name: "system", Label: "System", Diff: false, Stacked: true, Type: "float64"},
		},
	},
	"docker.memory.#": {
		Label: "Docker Memory",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "cache", Label: "Cache", Diff: false, Stacked: true},
			{Name: "rss", Label: "RSS", Diff: false, Stacked: true},
		},
	},
	"docker.blkio.io_queued.#": {
		Label: "Docker BlkIO Queued",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "read", Label: "Read", Diff: false, Stacked: true},
			{Name: "write", Label: "Write", Diff: false, Stacked: true},
			{Name: "sync", Label: "Sync", Diff: false, Stacked: true},
			{Name: "async", Label: "Async", Diff: false, Stacked: true},
		},
	},
	"docker.blkio.io_serviced.#": {
		Label: "Docker BlkIO IOPS",
		Unit:  "iops",
		Metrics: []mp.Metrics{
			{Name: "read", Label: "Read", Diff: true, Stacked: true, Type: "uint64", Scale: (1.0 / 60.0)},
			{Name: "write", Label: "Write", Diff: true, Stacked: true, Type: "uint64", Scale: (1.0 / 60.0)},
			{Name: "sync", Label: "Sync", Diff: true, Stacked: true, Type: "uint64", Scale: (1.0 / 60.0)},
			{Name: "async", Label: "Async", Diff: true, Stacked: true, Type: "uint64", Scale: (1.0 / 60.0)},
		},
	},
	"docker.blkio.io_service_bytes.#": {
		Label: "Docker BlkIO Bytes",
		Unit:  "bytes",
		Metrics: []mp.Metrics{
			{Name: "read", Label: "Read", Diff: true, Stacked: true, Type: "uint64"},
			{Name: "write", Label: "Write", Diff: true, Stacked: true, Type: "uint64"},
			{Name: "sync", Label: "Sync", Diff: true, Stacked: true, Type: "uint64"},
			{Name: "async", Label: "Async", Diff: true, Stacked: true, Type: "uint64"},
		},
	},
}
type DockerPlugin struct {
	Host             string
	DockerCommand    string
	Tempfile         string
	Method           string
	NameFormat       string
	Label            string
	pathBuilder      *pathBuilder
	lastMetricValues mp.MetricValues
	UseCPUPercentage bool
}
func getFile(path string) (string, error) {
	cmd := exec.Command("cat", path)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return "", err
	}
	return out.String(), nil
}
func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}
var normalizeMetricRe = regexp.MustCompile(`[^-a-zA-Z0-9_]`)
func normalizeMetricName(str string) string {
	return normalizeMetricRe.ReplaceAllString(str, "_")
}
func (m DockerPlugin) getDockerPs() (string, error) {
	cmd := exec.Command(m.DockerCommand, "--host", m.Host, "ps", "--no-trunc")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return "", err
	}
	return out.String(), nil
}
func (m DockerPlugin) listContainer() ([]docker.APIContainers, error) {
	client, _ := docker.NewClient(m.Host)
	containers, err := client.ListContainers(docker.ListContainersOptions{})
	if err != nil {
		return nil, err
	}
	return containers, nil
}
func findPrefixPath() (string, error) {
	pathCandidate := []string{"/host/cgroup", "/cgroup", "/host/sys/fs/cgroup", "/sys/fs/cgroup"}
	for _, path := range pathCandidate {
		result, err := exists(path)
		if err != nil {
			return "", err
		}
		if result {
			return path, nil
		}
	}
	return "", errors.New("no prefix path is found")
}
type pathBuilder struct {
	prefix   string
	pathType pathType
}
type pathType uint8
const (
	pathUnknown pathType = iota
	pathDocker
	pathDockerShort
	pathLxc
	pathSlice
)
func newPathBuilder() (*pathBuilder, error) {
	prefixPath, err := findPrefixPath()
	if err != nil {
		return nil, err
	}
	pathT, err := guessPathType(prefixPath)
	if err != nil {
		return nil, err
	}
	return &pathBuilder{
		prefix:   prefixPath,
		pathType: pathT,
	}, nil
}
func (pb *pathBuilder) build(id, metric, postfix string) string {
	switch pb.pathType {
	case pathDockerShort:
		return fmt.Sprintf("%s/docker/%s/%s.%s", pb.prefix, id, metric, postfix)
	case pathDocker:
		return fmt.Sprintf("%s/%s/docker/%s/%s.%s", pb.prefix, metric, id, metric, postfix)
	case pathLxc:
		return fmt.Sprintf("%s/%s/lxc/%s/%s.%s", pb.prefix, metric, id, metric, postfix)
	case pathSlice:
		return fmt.Sprintf("%s/%s/system.slice/docker-%s.scope/%s.%s", pb.prefix, metric, id, metric, postfix)
	default:
		return ""
	}
}
func guessPathType(prefix string) (pathType, error) {
	if ok, err := exists(prefix + "/memory/system.slice/"); ok && err == nil {
		return pathSlice, nil
	}
	if ok, err := exists(prefix + "/memory/docker/"); ok && err == nil {
		return pathDocker, nil
	}
	if ok, err := exists(prefix + "/memory/lxc/"); ok && err == nil {
		return pathLxc, nil
	}
	if ok, err := exists(prefix + "/docker/"); ok && err == nil {
		return pathDockerShort, nil
	}
	return pathUnknown, fmt.Errorf("can't resolve runtime metrics path")
}
func guessMethod(docker string) (string, error) {
	cmd := exec.Command(docker, "version")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return "", err
	}
	re := regexp.MustCompile(`Server API version: ([0-9]+)(?:\.([0-9]+))?`)
	res := re.FindAllStringSubmatch(out.String(), 1)
	if len(res) < 1 || len(res[0]) < 2 {
		return "API", nil
	}
	majorVer, err := strconv.Atoi(res[0][1])
	if err != nil {
		log.Printf("Use API because of failing to recognize version")
		return "API", nil
	}
	minorVer, err := strconv.Atoi(res[0][2])
	if err != nil {
		log.Printf("Use API because of failing to recognize version")
		return "API", nil
	}
	if majorVer == 1 && minorVer < 17 {
		return "File", nil
	}
	return "API", nil
}
func (m DockerPlugin) FetchMetrics() (map[string]interface{}, error) {
	var stats map[string]interface{}
	if m.Method == "API" {
		containers, err := m.listContainer()
		if err != nil {
			return nil, err
		}
		stats, err = m.FetchMetricsWithAPI(containers)
		if m.UseCPUPercentage {
			if time.Now().Sub(m.lastMetricValues.Timestamp) <= 5*time.Minute {
				addCPUPercentageStats(&stats, m.lastMetricValues.Values)
			}
		}
	} else {
		dockerStats := map[string][]string{}
		data, err := m.getDockerPs()
		if err != nil {
			return nil, err
		}
		lines := strings.Split(data, "\n")
		for n, line := range lines {
			if n == 0 {
				continue
			}
			fields := strings.Fields(line)
			if len(fields) > 3 {
				dockerStats[fields[0]] = []string{fields[1], fields[len(fields)-2]}
			}
		}
		stats, err = m.FetchMetricsWithFile(&dockerStats)
		if err != nil {
			return nil, err
		}
	}
	return stats, nil
}
func (m DockerPlugin) generateName(container docker.APIContainers) string {
	switch m.NameFormat {
	case "name_id":
		return fmt.Sprintf("%s_%s", strings.Replace(container.Names[0], "/", "", 1), container.ID[0:6])
	case "name":
		return strings.Replace(container.Names[0], "/", "", 1)
	case "id":
		return container.ID
	case "image":
		return container.Image
	case "image_id":
		return fmt.Sprintf("%s_%s", container.Image, container.ID[0:6])
	case "image_name":
		return fmt.Sprintf("%s_%s", container.Image, strings.Replace(container.Names[0], "/", "", 1))
	case "label":
		return container.Labels[m.Label]
	}
	return strings.Replace(container.Names[0], "/", "", 1)
}