-
Notifications
You must be signed in to change notification settings - Fork 18.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docker metrics (read metrics from cgroups for specified container) #8886
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -343,6 +343,19 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon | |
return job.Run() | ||
} | ||
|
||
func getContainersMetrics(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { | ||
if err := parseForm(r); err != nil { | ||
return err | ||
} | ||
if vars == nil { | ||
return fmt.Errorf("Missing parameter") | ||
} | ||
|
||
job := eng.Job("container_metrics", vars["name"]) | ||
streamJSON(job, w, false) | ||
return job.Run() | ||
} | ||
|
||
func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { | ||
if err := parseForm(r); err != nil { | ||
return err | ||
|
@@ -1262,6 +1275,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st | |
"/containers/{name:.*}/top": getContainersTop, | ||
"/containers/{name:.*}/logs": getContainersLogs, | ||
"/containers/{name:.*}/attach/ws": wsContainersAttach, | ||
"/containers/{name:.*}/metrics": getContainersMetrics, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I recall correctly, the agreement was to have a push/watch based interface for metrics in the docker daemon. Is that not a requirement anymore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct. We want a push approach where a client will subscribe to an docker endpoint and metrics will be pushed to the consumer with a requested internal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I understand we need to implement websocket endpoint. I think I can make Also we need to add customizable interval option with reasonable default Do we need to have template formatter to receive partial data? Maybe we also need to have special predefined formatters to output metrics Are there any public spec for this feature?
|
||
}, | ||
"POST": { | ||
"/auth": postAuth, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package daemon | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"io/ioutil" | ||
"path/filepath" | ||
|
||
"github.com/docker/docker/engine" | ||
"github.com/docker/libcontainer/cgroups" | ||
) | ||
|
||
var metricInfoTable map[string][]string | ||
|
||
func init() { | ||
metricInfoTable = map[string][]string{ | ||
"CpuUsage": {"cpuacct", "usage"}, | ||
"MemoryUsage": {"memory", "usage_in_bytes"}, | ||
"MemoryMaxUsage": {"memory", "max_usage_in_bytes"}, | ||
"MemoryLimit": {"memory", "limit_in_bytes"}, | ||
} | ||
} | ||
|
||
func (daemon *Daemon) ContainerMetrics(job *engine.Job) engine.Status { | ||
if len(job.Args) != 1 { | ||
return job.Errorf("usage: %s NAME", job.Name) | ||
} | ||
name := job.Args[0] | ||
if container := daemon.Get(name); container != nil { | ||
container.Lock() | ||
defer container.Unlock() | ||
|
||
out := &engine.Env{} | ||
out.Set("Id", container.ID) | ||
out.Set("Image", container.Image) | ||
out.Set("Name", container.Name) | ||
out.SetJson("State", container.State) | ||
|
||
metrics := map[string]string{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use the stats that libcontainer already exports? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. libcontainer's stats is limited only to native driver. Fetching metrics directly from cgroups is more portable. I think I need to move this code to drivers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is quite a bit of code in libcontainer to handle stats. I don't see a On Mon, Nov 3, 2014 at 2:03 PM, Gleb M Borisov notifications@github.com
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see any metrics in
We always used to run custom shell scripts to fetch lxc metrics from cgroups There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need
|
||
for metricKey, cgroupInfo := range metricInfoTable { | ||
cgroupSystem, cgroupKey := cgroupInfo[0], cgroupInfo[1] | ||
value, err := cgSubsystemValue(container.ID, cgroupSystem, cgroupKey) | ||
if err != nil { | ||
metrics[metricKey] = err.Error() | ||
} else { | ||
metrics[metricKey] = value | ||
} | ||
} | ||
out.SetJson("Metrics", metrics) | ||
|
||
if _, err := out.WriteTo(job.Stdout); err != nil { | ||
return job.Error(err) | ||
} | ||
return engine.StatusOK | ||
} | ||
return job.Errorf("No such container: %s", name) | ||
} | ||
|
||
func cgSubsystemValue(id, subsystem, key string) (string, error) { | ||
cgDir, err := cgSubsystemDir(id, subsystem) | ||
if err != nil { | ||
return "", err | ||
} | ||
data, err := ioutil.ReadFile(filepath.Join(cgDir, fmt.Sprintf("%s.%s", subsystem, key))) | ||
if err != nil { | ||
return "", err | ||
} | ||
return strings.Trim(string(data), "\r\n"+string(0)), nil | ||
} | ||
|
||
func cgSubsystemDir(id, subsystem string) (string, error) { | ||
cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
cgroupDir, err := cgroups.GetThisCgroupDir(subsystem) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
// With more recent lxc versions use, cgroup will be in lxc/, we'll search in bot | ||
dirnames := []string{ | ||
filepath.Join(cgroupRoot, cgroupDir, id), | ||
filepath.Join(cgroupRoot, cgroupDir, "docker", id), | ||
filepath.Join(cgroupRoot, cgroupDir, "lxc", id), | ||
} | ||
for i := range dirnames { | ||
if _, err := os.Stat(dirnames[i]); err == nil { | ||
return dirnames[i], nil | ||
} | ||
} | ||
|
||
return "", fmt.Errorf("Error: cgroup subsystem '%s' directory not found for container '%s'", subsystem, id) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use json.Unmarshal and a struct or map for this, please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's copy-paste from CmdInspect. I've marked both functions with TODO (will
extract common things and make their code less ugly).
Thanks for the tip, just a first week with Go :)
On Fri 31 Oct 2014 at 10:22 pm Erik Hollensbe notifications@github.com
wrote:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool. thanks. just let us know when it's ready for review again.