Merge pull request #2407 from eparis/cadvisor-0.5.0

Cadvisor 0.5.0
pull/6/head
Victor Marmol 2014-11-17 08:53:36 -08:00
commit 6908c9ca4a
9 changed files with 209 additions and 52 deletions

8
Godeps/Godeps.json generated
View File

@ -79,13 +79,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",

View File

@ -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.

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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))

View File

@ -15,4 +15,4 @@
package info
// Version of cAdvisor.
const VERSION = "0.4.0"
const VERSION = "0.5.0"

View File

@ -45,7 +45,7 @@ import (
"github.com/coreos/go-etcd/etcd"
"github.com/fsouza/go-dockerclient"
"github.com/golang/glog"
"github.com/google/cadvisor/client"
cadvisor "github.com/google/cadvisor/client"
)
const defaultRootDir = "/var/lib/kubelet"