diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 90f43249c9e..63f3fff143e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -69,13 +69,13 @@ }, { "ImportPath": "github.com/google/cadvisor/client", - "Comment": "0.4.0", - "Rev": "5a6d06c02600b1e57e55a9d9f71dbac1bfc9fe6c" + "Comment": "0.5.0", + "Rev": "8c4f650e62f096710da794e536de86e34447fca9" }, { "ImportPath": "github.com/google/cadvisor/info", - "Comment": "0.4.0", - "Rev": "5a6d06c02600b1e57e55a9d9f71dbac1bfc9fe6c" + "Comment": "0.5.0", + "Rev": "8c4f650e62f096710da794e536de86e34447fca9" }, { "ImportPath": "github.com/google/gofuzz", diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/client/README.md b/Godeps/_workspace/src/github.com/google/cadvisor/client/README.md new file mode 100644 index 00000000000..fededef1f07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/client/README.md @@ -0,0 +1,54 @@ +# Example REST API Client + +This is an implementation of a cAdvisor REST API in Go. You can use it like this: + +```go +client, err := client.NewClient("http://192.168.59.103:8080/") +``` + +Obviously, replace the URL with the path to your actual cAdvisor REST endpoint. + + +### MachineInfo + +```go +client.MachineInfo() +``` + +This method returns a cadvisor/info.MachineInfo struct with all the fields filled in. Here is an example return value: + +``` +(*info.MachineInfo)(0xc208022b10)({ + NumCores: (int) 4, + MemoryCapacity: (int64) 2106028032, + Filesystems: ([]info.FsInfo) (len=1 cap=4) { + (info.FsInfo) { + Device: (string) (len=9) "/dev/sda1", + Capacity: (uint64) 19507089408 + } + } +}) +``` + +You can see the full specification of the [MachineInfo struct in the source](../info/container.go) + +### ContainerInfo + +Given a container name and a ContainerInfoRequest, will return all information about the specified container. The ContainerInfoRequest struct just has one field, NumStats, which is the number of stat entries that you want returned. + +```go +request := info.ContainerInfoRequest{10} +sInfo, err := client.ContainerInfo("/docker/d9d3eb10179e6f93a...", &request) +``` +Returns a [ContainerInfo struct](../info/container.go) + +### SubcontainersInfo + +Given a container name and a ContainerInfoRequest, will recursively return all info about the container and all subcontainers contained within the container. The ContainerInfoRequest struct just has one field, NumStats, which is the number of stat entries that you want returned. + +```go +request := info.ContainerInfoRequest{10} +sInfo, err := client.SubcontainersInfo("/docker", &request) +``` + +Returns a [ContainerInfo struct](../info/container.go) with the Subcontainers field populated. diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go b/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go index f49176e3d4a..c1117d23252 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cadvisor +// TODO(cAdvisor): Package comment. +package client import ( "bytes" @@ -20,45 +21,75 @@ import ( "fmt" "io/ioutil" "net/http" + "path" "strings" "github.com/google/cadvisor/info" ) +// Client represents the base URL for a cAdvisor client. type Client struct { baseUrl string } -func NewClient(URL string) (*Client, error) { - c := &Client{ - baseUrl: strings.Join([]string{ - URL, - "api/v1.0", - }, "/"), +// NewClient returns a new client with the specified base URL. +func NewClient(url string) (*Client, error) { + if !strings.HasSuffix(url, "/") { + url += "/" } - return c, nil -} - -func (self *Client) machineInfoUrl() string { - return strings.Join([]string{self.baseUrl, "machine"}, "/") + + return &Client{ + baseUrl: fmt.Sprintf("%sapi/v1.1/", url), + }, nil } +// MachineInfo returns the JSON machine information for this client. +// A non-nil error result indicates a problem with obtaining +// the JSON machine information data. func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) { u := self.machineInfoUrl() ret := new(info.MachineInfo) - err = self.httpGetJsonData(ret, nil, u, "machine info") - if err != nil { + if err = self.httpGetJsonData(ret, nil, u, "machine info"); err != nil { return } minfo = ret return } -func (self *Client) containerInfoUrl(name string) string { - if name[0] == '/' { - name = name[1:] +// ContainerInfo returns the JSON container information for the specified +// container and request. +func (self *Client) ContainerInfo(name string, query *info.ContainerInfoRequest) (cinfo *info.ContainerInfo, err error) { + u := self.containerInfoUrl(name) + ret := new(info.ContainerInfo) + if err = self.httpGetJsonData(ret, query, u, fmt.Sprintf("container info for %q", name)); err != nil { + return } - return strings.Join([]string{self.baseUrl, "containers", name}, "/") + cinfo = ret + return +} + +// Returns the information about all subcontainers (recursive) of the specified container (including itself). +func (self *Client) SubcontainersInfo(name string, query *info.ContainerInfoRequest) ([]info.ContainerInfo, error) { + var response []info.ContainerInfo + url := self.subcontainersInfoUrl(name) + err := self.httpGetJsonData(&response, query, url, fmt.Sprintf("subcontainers container info for %q", name)) + if err != nil { + return []info.ContainerInfo{}, err + + } + return response, nil +} + +func (self *Client) machineInfoUrl() string { + return self.baseUrl + path.Join("machine") +} + +func (self *Client) containerInfoUrl(name string) string { + return self.baseUrl + path.Join("containers", name) +} + +func (self *Client) subcontainersInfoUrl(name string) string { + return self.baseUrl + path.Join("subcontainers", name) } func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error { @@ -84,23 +115,9 @@ func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName st err = fmt.Errorf("unable to read all %v: %v", infoName, err) return err } - err = json.Unmarshal(body, data) - if err != nil { + if err = json.Unmarshal(body, data); err != nil { err = fmt.Errorf("unable to unmarshal %v (%v): %v", infoName, string(body), err) return err } return nil } - -func (self *Client) ContainerInfo( - name string, - query *info.ContainerInfoRequest) (cinfo *info.ContainerInfo, err error) { - u := self.containerInfoUrl(name) - ret := new(info.ContainerInfo) - err = self.httpGetJsonData(ret, query, u, fmt.Sprintf("container info for %v", name)) - if err != nil { - return - } - cinfo = ret - return -} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go b/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go index eb6b7a45c87..35a75adb884 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cadvisor +package client import ( "encoding/json" "fmt" "net/http" "net/http/httptest" + "path" "reflect" "testing" "time" @@ -47,8 +48,7 @@ func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, repl if r.URL.Path == path { if expectedPostObj != nil { decoder := json.NewDecoder(r.Body) - err := decoder.Decode(expectedPostObjEmpty) - if err != nil { + if err := decoder.Decode(expectedPostObjEmpty); err != nil { t.Errorf("Received invalid object: %v", err) } if !reflect.DeepEqual(expectedPostObj, expectedPostObjEmpty) { @@ -57,7 +57,7 @@ func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, repl } encoder := json.NewEncoder(w) encoder.Encode(replyObj) - } else if r.URL.Path == "/api/v1.0/machine" { + } else if r.URL.Path == "/api/v1.1/machine" { fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`) } else { w.WriteHeader(http.StatusNotFound) @@ -72,12 +72,14 @@ func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, repl return client, ts, err } +// TestGetMachineInfo performs one test to check if MachineInfo() +// in a cAdvisor client returns the correct result. func TestGetMachineinfo(t *testing.T) { minfo := &info.MachineInfo{ NumCores: 8, MemoryCapacity: 31625871360, } - client, server, err := cadvisorTestClient("/api/v1.0/machine", nil, nil, minfo, t) + client, server, err := cadvisorTestClient("/api/v1.1/machine", nil, nil, minfo, t) if err != nil { t.Fatalf("unable to get a client %v", err) } @@ -91,13 +93,15 @@ func TestGetMachineinfo(t *testing.T) { } } +// TestGetContainerInfo generates a random container information object +// and then checks that ContainerInfo returns the expected result. func TestGetContainerInfo(t *testing.T) { query := &info.ContainerInfoRequest{ NumStats: 3, } containerName := "/some/container" cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second) - client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), query, &info.ContainerInfoRequest{}, cinfo, t) + client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.1/containers%v", containerName), query, &info.ContainerInfoRequest{}, cinfo, t) if err != nil { t.Fatalf("unable to get a client %v", err) } @@ -111,3 +115,40 @@ func TestGetContainerInfo(t *testing.T) { t.Error("received unexpected ContainerInfo") } } + +func TestGetSubcontainersInfo(t *testing.T) { + query := &info.ContainerInfoRequest{ + NumStats: 3, + } + containerName := "/some/container" + cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second) + cinfo1 := itest.GenerateRandomContainerInfo(path.Join(containerName, "sub1"), 4, query, 1*time.Second) + cinfo2 := itest.GenerateRandomContainerInfo(path.Join(containerName, "sub2"), 4, query, 1*time.Second) + response := []info.ContainerInfo{ + *cinfo, + *cinfo1, + *cinfo2, + } + client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.1/subcontainers%v", containerName), query, &info.ContainerInfoRequest{}, response, t) + if err != nil { + t.Fatalf("unable to get a client %v", err) + } + defer server.Close() + returned, err := client.SubcontainersInfo(containerName, query) + if err != nil { + t.Fatal(err) + } + + if len(returned) != 3 { + t.Errorf("unexpected number of results: got %d, expected 3", len(returned)) + } + if !returned[0].Eq(cinfo) { + t.Error("received unexpected ContainerInfo") + } + if !returned[1].Eq(cinfo1) { + t.Error("received unexpected ContainerInfo") + } + if !returned[2].Eq(cinfo2) { + t.Error("received unexpected ContainerInfo") + } +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/container.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/container.go index 7858fa5392d..5eaacb59df0 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/container.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/container.go @@ -40,8 +40,15 @@ type MemorySpec struct { } type ContainerSpec struct { - Cpu *CpuSpec `json:"cpu,omitempty"` - Memory *MemorySpec `json:"memory,omitempty"` + HasCpu bool `json:"has_cpu"` + Cpu CpuSpec `json:"cpu,omitempty"` + + HasMemory bool `json:"has_memory"` + Memory MemorySpec `json:"memory,omitempty"` + + HasNetwork bool `json:"has_network"` + + HasFilesystem bool `json:"has_filesystem"` } // Container reference contains enough information to uniquely identify a container @@ -66,7 +73,7 @@ type ContainerInfo struct { Subcontainers []ContainerReference `json:"subcontainers,omitempty"` // The isolation used in the container. - Spec *ContainerSpec `json:"spec,omitempty"` + Spec ContainerSpec `json:"spec,omitempty"` // Historical statistics gathered from the container. Stats []*ContainerStats `json:"stats,omitempty"` @@ -166,6 +173,19 @@ type CpuStats struct { Load int32 `json:"load"` } +type PerDiskStats struct { + Major uint64 `json:"major"` + Minor uint64 `json:"minor"` + Stats map[string]uint64 `json:"stats"` +} + +type DiskIoStats struct { + IoServiceBytes []PerDiskStats `json:"io_service_bytes,omitempty"` + IoServiced []PerDiskStats `json:"io_serviced,omitempty"` + IoQueued []PerDiskStats `json:"io_queued,omitempty"` + Sectors []PerDiskStats `json:"sectors,omitempty"` +} + type MemoryStats struct { // Memory limit, equivalent to "limit" in MemorySpec. // Units: Bytes. @@ -211,12 +231,26 @@ type NetworkStats struct { TxDropped uint64 `json:"tx_dropped"` } +type FsStats struct { + // The block device name associated with the filesystem. + Device string `json:"device,omitempty"` + + // Number of bytes that can be consumed by the container on this filesystem. + Limit uint64 `json:"capacity"` + + // Number of bytes that is consumed by the container on this filesystem. + Usage uint64 `json:"usage"` +} + type ContainerStats struct { // The time of this stat point. Timestamp time.Time `json:"timestamp"` Cpu *CpuStats `json:"cpu,omitempty"` + DiskIo DiskIoStats `json:"diskio,omitempty"` Memory *MemoryStats `json:"memory,omitempty"` Network *NetworkStats `json:"network,omitempty"` + // Filesystem statistics + Filesystem []FsStats `json:"filesystem,omitempty"` } // Makes a deep copy of the ContainerStats and returns a pointer to the new diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/machine.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/machine.go index 7415dc9edf7..0e73a8e0522 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/machine.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/machine.go @@ -14,12 +14,23 @@ package info +type FsInfo struct { + // Block device associated with the filesystem. + Device string `json:"device"` + + // Total number of bytes available on the filesystem. + Capacity uint64 `json:"capacity"` +} + type MachineInfo struct { // The number of cores in this machine. NumCores int `json:"num_cores"` // The amount of memory (in bytes) in this machine MemoryCapacity int64 `json:"memory_capacity"` + + // Filesystems on this machine. + Filesystems []FsInfo `json:"filesystems"` } type VersionInfo struct { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/test/datagen.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/test/datagen.go index 5d43ae578a0..bb0b2ff47e0 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/test/datagen.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/test/datagen.go @@ -51,10 +51,10 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info return ret } -func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec { - ret := &info.ContainerSpec{ - Cpu: &info.CpuSpec{}, - Memory: &info.MemorySpec{}, +func GenerateRandomContainerSpec(numCores int) info.ContainerSpec { + ret := info.ContainerSpec{ + Cpu: info.CpuSpec{}, + Memory: info.MemorySpec{}, } ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000)) ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000)) diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/version.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/version.go index 86757344d4d..8ae9b22712f 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/version.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/version.go @@ -15,4 +15,4 @@ package info // Version of cAdvisor. -const VERSION = "0.4.0" +const VERSION = "0.5.0"