Merge pull request #79 from monnand/query-parameter
User specified parameters for container info.
This commit is contained in:
commit
069cff84e2
@ -19,12 +19,13 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
"github.com/google/cadvisor/manager"
|
"github.com/google/cadvisor/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,9 +35,11 @@ const (
|
|||||||
MachineApi = "machine"
|
MachineApi = "machine"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
func HandleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
u := r.URL
|
||||||
|
|
||||||
// Get API request type.
|
// Get API request type.
|
||||||
requestType := u.Path[len(ApiResource):]
|
requestType := u.Path[len(ApiResource):]
|
||||||
i := strings.Index(requestType, "/")
|
i := strings.Index(requestType, "/")
|
||||||
@ -46,7 +49,8 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
|||||||
requestType = requestType[:i]
|
requestType = requestType[:i]
|
||||||
}
|
}
|
||||||
|
|
||||||
if requestType == MachineApi {
|
switch {
|
||||||
|
case requestType == MachineApi:
|
||||||
log.Printf("Api - Machine")
|
log.Printf("Api - Machine")
|
||||||
|
|
||||||
// Get the MachineInfo
|
// Get the MachineInfo
|
||||||
@ -60,14 +64,20 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
|||||||
fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err)
|
fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err)
|
||||||
}
|
}
|
||||||
w.Write(out)
|
w.Write(out)
|
||||||
} else if requestType == ContainersApi {
|
case requestType == ContainersApi:
|
||||||
// The container name is the path after the requestType
|
// The container name is the path after the requestType
|
||||||
containerName := requestArgs
|
containerName := requestArgs
|
||||||
|
|
||||||
log.Printf("Api - Container(%s)", containerName)
|
log.Printf("Api - Container(%s)", containerName)
|
||||||
|
|
||||||
|
var query info.ContainerInfoRequest
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&query)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return fmt.Errorf("unable to decode the json value: ", err)
|
||||||
|
}
|
||||||
// Get the container.
|
// Get the container.
|
||||||
cont, err := m.GetContainerInfo(containerName)
|
cont, err := m.GetContainerInfo(containerName, &query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err)
|
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err)
|
||||||
return err
|
return err
|
||||||
@ -79,7 +89,7 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
|||||||
fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err)
|
fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err)
|
||||||
}
|
}
|
||||||
w.Write(out)
|
w.Write(out)
|
||||||
} else {
|
default:
|
||||||
return fmt.Errorf("unknown API request type %q", requestType)
|
return fmt.Errorf("unknown API request type %q", requestType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func main() {
|
|||||||
|
|
||||||
// Handler for the API.
|
// Handler for the API.
|
||||||
http.HandleFunc(api.ApiResource, func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc(api.ApiResource, func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := api.HandleRequest(containerManager, w, r.URL)
|
err := api.HandleRequest(containerManager, w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "%s", err)
|
fmt.Fprintf(w, "%s", err)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package cadvisor
|
package cadvisor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -49,7 +50,7 @@ func (self *Client) machineInfoUrl() string {
|
|||||||
func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) {
|
func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) {
|
||||||
u := self.machineInfoUrl()
|
u := self.machineInfoUrl()
|
||||||
ret := new(info.MachineInfo)
|
ret := new(info.MachineInfo)
|
||||||
err = self.httpGetJsonData(ret, u, "machine info")
|
err = self.httpGetJsonData(ret, nil, u, "machine info")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -64,8 +65,19 @@ func (self *Client) containerInfoUrl(name string) string {
|
|||||||
return strings.Join([]string{self.baseUrl, "containers", name}, "/")
|
return strings.Join([]string{self.baseUrl, "containers", name}, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Client) httpGetJsonData(data interface{}, url, infoName string) error {
|
func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error {
|
||||||
resp, err := http.Get(url)
|
var resp *http.Response
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if postData != nil {
|
||||||
|
data, err := json.Marshal(postData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshal data: %v", err)
|
||||||
|
}
|
||||||
|
resp, err = http.Post(url, "application/json", bytes.NewBuffer(data))
|
||||||
|
} else {
|
||||||
|
resp, err = http.Get(url)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("unable to get %v: %v", infoName, err)
|
err = fmt.Errorf("unable to get %v: %v", infoName, err)
|
||||||
return err
|
return err
|
||||||
@ -84,10 +96,12 @@ func (self *Client) httpGetJsonData(data interface{}, url, infoName string) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Client) ContainerInfo(name string) (cinfo *info.ContainerInfo, err error) {
|
func (self *Client) ContainerInfo(
|
||||||
|
name string,
|
||||||
|
query *info.ContainerInfoRequest) (cinfo *info.ContainerInfo, err error) {
|
||||||
u := self.containerInfoUrl(name)
|
u := self.containerInfoUrl(name)
|
||||||
ret := new(info.ContainerInfo)
|
ret := new(info.ContainerInfo)
|
||||||
err = self.httpGetJsonData(ret, u, fmt.Sprintf("container info for %v", name))
|
err = self.httpGetJsonData(ret, query, u, fmt.Sprintf("container info for %v", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -21,33 +21,42 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/cadvisor/info"
|
"github.com/google/cadvisor/info"
|
||||||
|
itest "github.com/google/cadvisor/info/test"
|
||||||
|
"github.com/kr/pretty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testGetJsonData(
|
func testGetJsonData(
|
||||||
strRep string,
|
expected interface{},
|
||||||
emptyData interface{},
|
|
||||||
f func() (interface{}, error),
|
f func() (interface{}, error),
|
||||||
) error {
|
) error {
|
||||||
err := json.Unmarshal([]byte(strRep), emptyData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid json input: %v", err)
|
|
||||||
}
|
|
||||||
reply, err := f()
|
reply, err := f()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to retrieve data: %v", err)
|
return fmt.Errorf("unable to retrieve data: %v", err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(reply, emptyData) {
|
if !reflect.DeepEqual(reply, expected) {
|
||||||
return fmt.Errorf("retrieved wrong data: %+v != %+v", reply, emptyData)
|
return pretty.Errorf("retrieved wrong data: %# v != %# v", reply, expected)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) {
|
func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == path {
|
if r.URL.Path == path {
|
||||||
fmt.Fprint(w, reply)
|
if expectedPostObj != nil {
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(expectedPostObjEmpty)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Recieved invalid object: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expectedPostObj, expectedPostObjEmpty) {
|
||||||
|
t.Errorf("Recieved unexpected object: %+v", expectedPostObjEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.Encode(replyObj)
|
||||||
} else if r.URL.Path == "/api/v1.0/machine" {
|
} else if r.URL.Path == "/api/v1.0/machine" {
|
||||||
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`)
|
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`)
|
||||||
} else {
|
} else {
|
||||||
@ -64,693 +73,69 @@ func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMachineinfo(t *testing.T) {
|
func TestGetMachineinfo(t *testing.T) {
|
||||||
respStr := `{"num_cores":8,"memory_capacity":31625871360}`
|
minfo := &info.MachineInfo{
|
||||||
client, server, err := cadvisorTestClient("/api/v1.0/machine", respStr)
|
NumCores: 8,
|
||||||
|
MemoryCapacity: 31625871360,
|
||||||
|
}
|
||||||
|
client, server, err := cadvisorTestClient("/api/v1.0/machine", nil, nil, minfo, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get a client %v", err)
|
t.Fatalf("unable to get a client %v", err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
err = testGetJsonData(respStr, &info.MachineInfo{}, func() (interface{}, error) {
|
returned, err := client.MachineInfo()
|
||||||
return client.MachineInfo()
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(returned, minfo) {
|
||||||
|
t.Fatalf("received unexpected machine info")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContainerInfo(t *testing.T) {
|
func TestGetContainerInfo(t *testing.T) {
|
||||||
respStr := `
|
query := &info.ContainerInfoRequest{
|
||||||
{
|
NumStats: 3,
|
||||||
"name": "%v",
|
NumSamples: 2,
|
||||||
"spec": {
|
CpuUsagePercentiles: []int{10, 50, 90},
|
||||||
"cpu": {
|
MemoryUsagePercentages: []int{10, 80, 90},
|
||||||
"limit": 18446744073709551000,
|
|
||||||
"max_limit": 0,
|
|
||||||
"mask": {
|
|
||||||
"data": [
|
|
||||||
18446744073709551000
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"limit": 18446744073709551000,
|
|
||||||
"swap_limit": 18446744073709551000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"stats": [
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:26.434981825Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:27.538394608Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:28.640302072Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:29.74247308Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:30.844494537Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:31.946757066Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:33.050214062Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:34.15222186Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:35.25417391Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:36.355902169Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:37.457585928Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:38.559417379Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:39.662978029Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:40.764671232Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:41.866456459Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu_usage": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stats_summary": {
|
|
||||||
"max_memory_usage": 495616,
|
|
||||||
"samples": [
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:27.538394608Z",
|
|
||||||
"duration": 1103412783,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:28.640302072Z",
|
|
||||||
"duration": 1101907464,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:29.74247308Z",
|
|
||||||
"duration": 1102171008,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:30.844494537Z",
|
|
||||||
"duration": 1102021457,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:31.946757066Z",
|
|
||||||
"duration": 1102262529,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:33.050214062Z",
|
|
||||||
"duration": 1103456996,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:34.15222186Z",
|
|
||||||
"duration": 1102007798,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:35.25417391Z",
|
|
||||||
"duration": 1101952050,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:36.355902169Z",
|
|
||||||
"duration": 1101728259,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:37.457585928Z",
|
|
||||||
"duration": 1101683759,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:38.559417379Z",
|
|
||||||
"duration": 1101831451,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:39.662978029Z",
|
|
||||||
"duration": 1103560650,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:40.764671232Z",
|
|
||||||
"duration": 1101693203,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:41.866456459Z",
|
|
||||||
"duration": 1101785227,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"memory_usage_percentiles": [
|
|
||||||
{
|
|
||||||
"percentage": 50,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 80,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 90,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 95,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 99,
|
|
||||||
"value": 495616
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cpu_usage_percentiles": [
|
|
||||||
{
|
|
||||||
"percentage": 50,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 80,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 90,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 95,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 99,
|
|
||||||
"value": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
containerName := "/some/container"
|
containerName := "/some/container"
|
||||||
respStr = fmt.Sprintf(respStr, containerName)
|
cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second)
|
||||||
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), respStr)
|
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), query, &info.ContainerInfoRequest{}, cinfo, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get a client %v", err)
|
t.Fatalf("unable to get a client %v", err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
err = testGetJsonData(respStr, &info.ContainerInfo{}, func() (interface{}, error) {
|
returned, err := client.ContainerInfo(containerName, query)
|
||||||
return client.ContainerInfo(containerName)
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We cannot use DeepEqual() to compare them directly,
|
||||||
|
// because json en/decoded time may have precision issues.
|
||||||
|
if !reflect.DeepEqual(returned.ContainerReference, cinfo.ContainerReference) {
|
||||||
|
t.Errorf("received unexpected container ref")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(returned.Subcontainers, cinfo.Subcontainers) {
|
||||||
|
t.Errorf("received unexpected subcontainers")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(returned.Spec, cinfo.Spec) {
|
||||||
|
t.Errorf("received unexpected spec")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(returned.StatsPercentiles, cinfo.StatsPercentiles) {
|
||||||
|
t.Errorf("received unexpected spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, expectedStats := range cinfo.Stats {
|
||||||
|
returnedStats := returned.Stats[i]
|
||||||
|
if !expectedStats.Eq(returnedStats) {
|
||||||
|
t.Errorf("received unexpected stats")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, expectedSample := range cinfo.Samples {
|
||||||
|
returnedSample := returned.Samples[i]
|
||||||
|
if !expectedSample.Eq(returnedSample) {
|
||||||
|
t.Errorf("received unexpected sample")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package info
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -57,6 +58,40 @@ type ContainerReference struct {
|
|||||||
Aliases []string `json:"aliases,omitempty"`
|
Aliases []string `json:"aliases,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerInfoQuery is used when users check a container info from the REST api.
|
||||||
|
// It specifies how much data users want to get about a container
|
||||||
|
type ContainerInfoRequest struct {
|
||||||
|
// Max number of stats to return.
|
||||||
|
NumStats int `json:"num_stats,omitempty"`
|
||||||
|
// Max number of samples to return.
|
||||||
|
NumSamples int `json:"num_samples,omitempty"`
|
||||||
|
|
||||||
|
// Different percentiles of CPU usage within a period. The values must be within [0, 100]
|
||||||
|
CpuUsagePercentiles []int `json:"cpu_usage_percentiles,omitempty"`
|
||||||
|
// Different percentiles of memory usage within a period. The values must be within [0, 100]
|
||||||
|
MemoryUsagePercentages []int `json:"memory_usage_percentiles,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ContainerInfoRequest) FillDefaults() *ContainerInfoRequest {
|
||||||
|
ret := self
|
||||||
|
if ret == nil {
|
||||||
|
ret = new(ContainerInfoRequest)
|
||||||
|
}
|
||||||
|
if ret.NumStats <= 0 {
|
||||||
|
ret.NumStats = 1024
|
||||||
|
}
|
||||||
|
if ret.NumSamples <= 0 {
|
||||||
|
ret.NumSamples = 1024
|
||||||
|
}
|
||||||
|
if len(ret.CpuUsagePercentiles) == 0 {
|
||||||
|
ret.CpuUsagePercentiles = []int{50, 80, 90, 99}
|
||||||
|
}
|
||||||
|
if len(ret.MemoryUsagePercentages) == 0 {
|
||||||
|
ret.MemoryUsagePercentages = []int{50, 80, 90, 99}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerInfo struct {
|
type ContainerInfo struct {
|
||||||
ContainerReference
|
ContainerReference
|
||||||
|
|
||||||
@ -219,6 +254,67 @@ type ContainerStatsSample struct {
|
|||||||
} `json:"memory"`
|
} `json:"memory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {
|
||||||
|
// t1 should not be later than t2
|
||||||
|
if t1.After(t2) {
|
||||||
|
t1, t2 = t2, t1
|
||||||
|
}
|
||||||
|
diff := t2.Sub(t1)
|
||||||
|
if diff <= tolerance {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func durationEq(a, b time.Duration, tolerance time.Duration) bool {
|
||||||
|
if a > b {
|
||||||
|
a, b = b, a
|
||||||
|
}
|
||||||
|
diff := a - b
|
||||||
|
if diff <= tolerance {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 10ms, i.e. 0.01s
|
||||||
|
timePrecision time.Duration = 10 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function is useful because we do not require precise time
|
||||||
|
// representation.
|
||||||
|
func (a *ContainerStats) Eq(b *ContainerStats) bool {
|
||||||
|
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Memory, b.Memory) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is useful because we do not require precise time
|
||||||
|
// representation.
|
||||||
|
func (a *ContainerStatsSample) Eq(b *ContainerStatsSample) bool {
|
||||||
|
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !durationEq(a.Duration, b.Duration, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Memory, b.Memory) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type Percentile struct {
|
type Percentile struct {
|
||||||
Percentage int `json:"percentage"`
|
Percentage int `json:"percentage"`
|
||||||
Value uint64 `json:"value"`
|
Value uint64 `json:"value"`
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
@ -46,6 +47,7 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info
|
|||||||
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
|
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
|
||||||
stats.Cpu.Usage.System = 0
|
stats.Cpu.Usage.System = 0
|
||||||
stats.Memory.Usage = uint64(rand.Int63n(4096))
|
stats.Memory.Usage = uint64(rand.Int63n(4096))
|
||||||
|
ret[i] = stats
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@ -66,3 +68,60 @@ func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec {
|
|||||||
ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
|
ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateRandomContainerInfo(containerName string, numCores int, query *info.ContainerInfoRequest, duration time.Duration) *info.ContainerInfo {
|
||||||
|
stats := GenerateRandomStats(query.NumStats, numCores, duration)
|
||||||
|
samples, _ := NewSamplesFromStats(stats...)
|
||||||
|
if len(samples) > query.NumSamples {
|
||||||
|
samples = samples[:query.NumSamples]
|
||||||
|
}
|
||||||
|
cpuPercentiles := make([]info.Percentile, 0, len(query.CpuUsagePercentiles))
|
||||||
|
|
||||||
|
// TODO(monnand): This will generate percentiles where 50%tile data may
|
||||||
|
// be larger than 90%tile data.
|
||||||
|
for _, p := range query.CpuUsagePercentiles {
|
||||||
|
percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
|
||||||
|
cpuPercentiles = append(cpuPercentiles, percentile)
|
||||||
|
}
|
||||||
|
memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentages))
|
||||||
|
for _, p := range query.MemoryUsagePercentages {
|
||||||
|
percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
|
||||||
|
memPercentiles = append(memPercentiles, percentile)
|
||||||
|
}
|
||||||
|
|
||||||
|
percentiles := &info.ContainerStatsPercentiles{
|
||||||
|
MaxMemoryUsage: uint64(rand.Int63n(4096)),
|
||||||
|
MemoryUsagePercentiles: memPercentiles,
|
||||||
|
CpuUsagePercentiles: cpuPercentiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := GenerateRandomContainerSpec(numCores)
|
||||||
|
|
||||||
|
ret := &info.ContainerInfo{
|
||||||
|
ContainerReference: info.ContainerReference{
|
||||||
|
Name: containerName,
|
||||||
|
},
|
||||||
|
Spec: spec,
|
||||||
|
StatsPercentiles: percentiles,
|
||||||
|
Samples: samples,
|
||||||
|
Stats: stats,
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSamplesFromStats(stats ...*info.ContainerStats) ([]*info.ContainerStatsSample, error) {
|
||||||
|
if len(stats) < 2 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
samples := make([]*info.ContainerStatsSample, 0, len(stats)-1)
|
||||||
|
for i, s := range stats[1:] {
|
||||||
|
prev := stats[i]
|
||||||
|
sample, err := info.NewSample(prev, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to generate sample from %+v and %+v: %v",
|
||||||
|
prev, s, err)
|
||||||
|
}
|
||||||
|
samples = append(samples, sample)
|
||||||
|
}
|
||||||
|
return samples, nil
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ type Manager interface {
|
|||||||
Start() error
|
Start() error
|
||||||
|
|
||||||
// Get information about a container.
|
// Get information about a container.
|
||||||
GetContainerInfo(containerName string) (*info.ContainerInfo, error)
|
GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
|
|
||||||
// Get information about the machine.
|
// Get information about the machine.
|
||||||
GetMachineInfo() (*info.MachineInfo, error)
|
GetMachineInfo() (*info.MachineInfo, error)
|
||||||
@ -106,8 +106,8 @@ func (m *manager) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get a container by name.
|
// Get a container by name.
|
||||||
func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, error) {
|
func (m *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
log.Printf("Get(%s)", containerName)
|
log.Printf("Get(%s); %+v", containerName, query)
|
||||||
var cont *containerData
|
var cont *containerData
|
||||||
var ok bool
|
var ok bool
|
||||||
func() {
|
func() {
|
||||||
@ -130,21 +130,21 @@ func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, e
|
|||||||
var percentiles *info.ContainerStatsPercentiles
|
var percentiles *info.ContainerStatsPercentiles
|
||||||
var samples []*info.ContainerStatsSample
|
var samples []*info.ContainerStatsSample
|
||||||
var stats []*info.ContainerStats
|
var stats []*info.ContainerStats
|
||||||
// TODO(monnand): These numbers should not be hard coded
|
query = query.FillDefaults()
|
||||||
percentiles, err = m.storageDriver.Percentiles(
|
percentiles, err = m.storageDriver.Percentiles(
|
||||||
cinfo.Name,
|
cinfo.Name,
|
||||||
[]int{50, 80, 90, 99},
|
query.CpuUsagePercentiles,
|
||||||
[]int{50, 80, 90, 99},
|
query.MemoryUsagePercentages,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
samples, err = m.storageDriver.Samples(cinfo.Name, 1024)
|
samples, err = m.storageDriver.Samples(cinfo.Name, query.NumSamples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err = m.storageDriver.RecentStats(cinfo.Name, 1024)
|
stats, err = m.storageDriver.RecentStats(cinfo.Name, query.NumStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
253
manager/manager_test.go
Normal file
253
manager/manager_test.go
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Per-container manager.
|
||||||
|
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/container"
|
||||||
|
ctest "github.com/google/cadvisor/container/test"
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
itest "github.com/google/cadvisor/info/test"
|
||||||
|
stest "github.com/google/cadvisor/storage/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createManagerAndAddContainers(
|
||||||
|
driver *stest.MockStorageDriver,
|
||||||
|
containers []string,
|
||||||
|
f func(*ctest.MockContainerHandler),
|
||||||
|
t *testing.T,
|
||||||
|
) *manager {
|
||||||
|
if driver == nil {
|
||||||
|
driver = &stest.MockStorageDriver{}
|
||||||
|
}
|
||||||
|
factory := &ctest.FactoryForMockContainerHandler{
|
||||||
|
Name: "factoryForManager",
|
||||||
|
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) {
|
||||||
|
handler.Name = name
|
||||||
|
found := false
|
||||||
|
for _, c := range containers {
|
||||||
|
if c == name {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Asked to create a container with name %v, which is unknown.", name)
|
||||||
|
}
|
||||||
|
f(handler)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
container.RegisterContainerHandlerFactory("/", factory)
|
||||||
|
mif, err := New(driver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ret, ok := mif.(*manager); ok {
|
||||||
|
for _, container := range containers {
|
||||||
|
ret.containers[container], err = NewContainerData(container, driver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
t.Fatal("Wrong type")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetContainerInfo(t *testing.T) {
|
||||||
|
containers := []string{
|
||||||
|
"/c1",
|
||||||
|
"/c2",
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &info.ContainerInfoRequest{
|
||||||
|
NumStats: 256,
|
||||||
|
NumSamples: 128,
|
||||||
|
CpuUsagePercentiles: []int{10, 50, 90},
|
||||||
|
MemoryUsagePercentages: []int{10, 80, 90},
|
||||||
|
}
|
||||||
|
|
||||||
|
infosMap := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &stest.MockStorageDriver{}
|
||||||
|
m := createManagerAndAddContainers(
|
||||||
|
driver,
|
||||||
|
containers,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
cinfo := infosMap[h.Name]
|
||||||
|
stats := cinfo.Stats
|
||||||
|
samples := cinfo.Samples
|
||||||
|
percentiles := cinfo.StatsPercentiles
|
||||||
|
spec := cinfo.Spec
|
||||||
|
driver.On(
|
||||||
|
"Percentiles",
|
||||||
|
h.Name,
|
||||||
|
query.CpuUsagePercentiles,
|
||||||
|
query.MemoryUsagePercentages,
|
||||||
|
).Return(
|
||||||
|
percentiles,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"Samples",
|
||||||
|
h.Name,
|
||||||
|
query.NumSamples,
|
||||||
|
).Return(
|
||||||
|
samples,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"RecentStats",
|
||||||
|
h.Name,
|
||||||
|
query.NumStats,
|
||||||
|
).Return(
|
||||||
|
stats,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
h.On("ListContainers", container.LIST_SELF).Return(
|
||||||
|
[]info.ContainerReference(nil),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
h.On("GetSpec").Return(
|
||||||
|
spec,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
handlerMap[h.Name] = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
returnedInfos := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
cinfo, err := m.GetContainerInfo(container, query)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to get info for container %v: %v", container, err)
|
||||||
|
}
|
||||||
|
returnedInfos[container] = cinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
for container, handler := range handlerMap {
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
returned := returnedInfos[container]
|
||||||
|
expected := infosMap[container]
|
||||||
|
if !reflect.DeepEqual(returned, expected) {
|
||||||
|
t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetContainerInfoWithDefaultValue(t *testing.T) {
|
||||||
|
containers := []string{
|
||||||
|
"/c1",
|
||||||
|
"/c2",
|
||||||
|
}
|
||||||
|
|
||||||
|
var query *info.ContainerInfoRequest
|
||||||
|
query = query.FillDefaults()
|
||||||
|
|
||||||
|
infosMap := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &stest.MockStorageDriver{}
|
||||||
|
m := createManagerAndAddContainers(
|
||||||
|
driver,
|
||||||
|
containers,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
cinfo := infosMap[h.Name]
|
||||||
|
stats := cinfo.Stats
|
||||||
|
samples := cinfo.Samples
|
||||||
|
percentiles := cinfo.StatsPercentiles
|
||||||
|
spec := cinfo.Spec
|
||||||
|
driver.On(
|
||||||
|
"Percentiles",
|
||||||
|
h.Name,
|
||||||
|
query.CpuUsagePercentiles,
|
||||||
|
query.MemoryUsagePercentages,
|
||||||
|
).Return(
|
||||||
|
percentiles,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"Samples",
|
||||||
|
h.Name,
|
||||||
|
query.NumSamples,
|
||||||
|
).Return(
|
||||||
|
samples,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"RecentStats",
|
||||||
|
h.Name,
|
||||||
|
query.NumStats,
|
||||||
|
).Return(
|
||||||
|
stats,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
h.On("ListContainers", container.LIST_SELF).Return(
|
||||||
|
[]info.ContainerReference(nil),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
h.On("GetSpec").Return(
|
||||||
|
spec,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
handlerMap[h.Name] = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
returnedInfos := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
// nil should give us default values
|
||||||
|
cinfo, err := m.GetContainerInfo(container, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to get info for container %v: %v", container, err)
|
||||||
|
}
|
||||||
|
returnedInfos[container] = cinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
for container, handler := range handlerMap {
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
returned := returnedInfos[container]
|
||||||
|
expected := infosMap[container]
|
||||||
|
if !reflect.DeepEqual(returned, expected) {
|
||||||
|
t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -162,7 +162,11 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
|
|||||||
containerName := u.Path[len(ContainersPage)-1:]
|
containerName := u.Path[len(ContainersPage)-1:]
|
||||||
|
|
||||||
// Get the container.
|
// Get the container.
|
||||||
cont, err := m.GetContainerInfo(containerName)
|
reqParams := info.ContainerInfoRequest{
|
||||||
|
NumStats: 60,
|
||||||
|
NumSamples: 60,
|
||||||
|
}
|
||||||
|
cont, err := m.GetContainerInfo(containerName, &reqParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
|
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user