Merge pull request #79 from monnand/query-parameter

User specified parameters for container info.
This commit is contained in:
Victor Marmol 2014-07-10 12:36:33 -07:00
commit 069cff84e2
9 changed files with 523 additions and 702 deletions

View File

@ -19,12 +19,13 @@ package api
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/cadvisor/info"
"github.com/google/cadvisor/manager"
)
@ -34,9 +35,11 @@ const (
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()
u := r.URL
// Get API request type.
requestType := u.Path[len(ApiResource):]
i := strings.Index(requestType, "/")
@ -46,7 +49,8 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
requestType = requestType[:i]
}
if requestType == MachineApi {
switch {
case requestType == MachineApi:
log.Printf("Api - Machine")
// 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)
}
w.Write(out)
} else if requestType == ContainersApi {
case requestType == ContainersApi:
// The container name is the path after the requestType
containerName := requestArgs
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.
cont, err := m.GetContainerInfo(containerName)
cont, err := m.GetContainerInfo(containerName, &query)
if err != nil {
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, 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)
}
w.Write(out)
} else {
default:
return fmt.Errorf("unknown API request type %q", requestType)
}

View File

@ -81,7 +81,7 @@ func main() {
// Handler for the API.
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 {
fmt.Fprintf(w, "%s", err)
}

View File

@ -15,6 +15,7 @@
package cadvisor
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
@ -49,7 +50,7 @@ func (self *Client) machineInfoUrl() string {
func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) {
u := self.machineInfoUrl()
ret := new(info.MachineInfo)
err = self.httpGetJsonData(ret, u, "machine info")
err = self.httpGetJsonData(ret, nil, u, "machine info")
if err != nil {
return
}
@ -64,8 +65,19 @@ func (self *Client) containerInfoUrl(name string) string {
return strings.Join([]string{self.baseUrl, "containers", name}, "/")
}
func (self *Client) httpGetJsonData(data interface{}, url, infoName string) error {
resp, err := http.Get(url)
func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error {
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 {
err = fmt.Errorf("unable to get %v: %v", infoName, err)
return err
@ -84,10 +96,12 @@ func (self *Client) httpGetJsonData(data interface{}, url, infoName string) erro
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)
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 {
return
}

View File

@ -21,33 +21,42 @@ import (
"net/http/httptest"
"reflect"
"testing"
"time"
"github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test"
"github.com/kr/pretty"
)
func testGetJsonData(
strRep string,
emptyData interface{},
expected interface{},
f func() (interface{}, error),
) error {
err := json.Unmarshal([]byte(strRep), emptyData)
if err != nil {
return fmt.Errorf("invalid json input: %v", err)
}
reply, err := f()
if err != nil {
return fmt.Errorf("unable to retrieve data: %v", err)
}
if !reflect.DeepEqual(reply, emptyData) {
return fmt.Errorf("retrieved wrong data: %+v != %+v", reply, emptyData)
if !reflect.DeepEqual(reply, expected) {
return pretty.Errorf("retrieved wrong data: %# v != %# v", reply, expected)
}
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) {
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" {
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`)
} else {
@ -64,693 +73,69 @@ func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) {
}
func TestGetMachineinfo(t *testing.T) {
respStr := `{"num_cores":8,"memory_capacity":31625871360}`
client, server, err := cadvisorTestClient("/api/v1.0/machine", respStr)
minfo := &info.MachineInfo{
NumCores: 8,
MemoryCapacity: 31625871360,
}
client, server, err := cadvisorTestClient("/api/v1.0/machine", nil, nil, minfo, t)
if err != nil {
t.Fatalf("unable to get a client %v", err)
}
defer server.Close()
err = testGetJsonData(respStr, &info.MachineInfo{}, func() (interface{}, error) {
return client.MachineInfo()
})
returned, err := client.MachineInfo()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(returned, minfo) {
t.Fatalf("received unexpected machine info")
}
}
func TestGetContainerInfo(t *testing.T) {
respStr := `
{
"name": "%v",
"spec": {
"cpu": {
"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
}
]
}
}
`
query := &info.ContainerInfoRequest{
NumStats: 3,
NumSamples: 2,
CpuUsagePercentiles: []int{10, 50, 90},
MemoryUsagePercentages: []int{10, 80, 90},
}
containerName := "/some/container"
respStr = fmt.Sprintf(respStr, containerName)
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), respStr)
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)
if err != nil {
t.Fatalf("unable to get a client %v", err)
}
defer server.Close()
err = testGetJsonData(respStr, &info.ContainerInfo{}, func() (interface{}, error) {
return client.ContainerInfo(containerName)
})
returned, err := client.ContainerInfo(containerName, query)
if err != nil {
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")
}
}
}

View File

@ -16,6 +16,7 @@ package info
import (
"fmt"
"reflect"
"sort"
"time"
)
@ -57,6 +58,40 @@ type ContainerReference struct {
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 {
ContainerReference
@ -219,6 +254,67 @@ type ContainerStatsSample struct {
} `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 {
Percentage int `json:"percentage"`
Value uint64 `json:"value"`

View File

@ -15,6 +15,7 @@
package test
import (
"fmt"
"math"
"math/rand"
"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.System = 0
stats.Memory.Usage = uint64(rand.Int63n(4096))
ret[i] = stats
}
return ret
}
@ -66,3 +68,60 @@ func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec {
ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
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
}

View File

@ -30,7 +30,7 @@ type Manager interface {
Start() error
// 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.
GetMachineInfo() (*info.MachineInfo, error)
@ -106,8 +106,8 @@ func (m *manager) Start() error {
}
// Get a container by name.
func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, error) {
log.Printf("Get(%s)", containerName)
func (m *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
log.Printf("Get(%s); %+v", containerName, query)
var cont *containerData
var ok bool
func() {
@ -130,21 +130,21 @@ func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, e
var percentiles *info.ContainerStatsPercentiles
var samples []*info.ContainerStatsSample
var stats []*info.ContainerStats
// TODO(monnand): These numbers should not be hard coded
query = query.FillDefaults()
percentiles, err = m.storageDriver.Percentiles(
cinfo.Name,
[]int{50, 80, 90, 99},
[]int{50, 80, 90, 99},
query.CpuUsagePercentiles,
query.MemoryUsagePercentages,
)
if err != nil {
return nil, err
}
samples, err = m.storageDriver.Samples(cinfo.Name, 1024)
samples, err = m.storageDriver.Samples(cinfo.Name, query.NumSamples)
if err != nil {
return nil, err
}
stats, err = m.storageDriver.RecentStats(cinfo.Name, 1024)
stats, err = m.storageDriver.RecentStats(cinfo.Name, query.NumStats)
if err != nil {
return nil, err
}

253
manager/manager_test.go Normal file
View 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)
}
}
}

View File

@ -162,7 +162,11 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
containerName := u.Path[len(ContainersPage)-1:]
// Get the container.
cont, err := m.GetContainerInfo(containerName)
reqParams := info.ContainerInfoRequest{
NumStats: 60,
NumSamples: 60,
}
cont, err := m.GetContainerInfo(containerName, &reqParams)
if err != nil {
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
}