mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Add percentage of used resources to node metrics.
This commit is contained in:
parent
5898f87722
commit
566af82be3
@ -25,6 +25,8 @@ import (
|
|||||||
|
|
||||||
"github.com/renstrom/dedent"
|
"github.com/renstrom/dedent"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TopNodeOptions contains all the options for running the top-node cli command.
|
// TopNodeOptions contains all the options for running the top-node cli command.
|
||||||
@ -95,6 +97,12 @@ func (o *TopNodeOptions) Validate() error {
|
|||||||
if len(o.ResourceName) > 0 && len(o.Selector) > 0 {
|
if len(o.ResourceName) > 0 && len(o.Selector) > 0 {
|
||||||
return errors.New("only one of NAME or --selector can be provided")
|
return errors.New("only one of NAME or --selector can be provided")
|
||||||
}
|
}
|
||||||
|
if len(o.Selector) > 0 {
|
||||||
|
_, err := labels.Parse(o.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,5 +111,36 @@ func (o TopNodeOptions) RunTopNode() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return o.Printer.PrintNodeMetrics(metrics)
|
|
||||||
|
selector := labels.Everything()
|
||||||
|
if len(o.Selector) > 0 {
|
||||||
|
selector, err = labels.Parse(o.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var nodes []api.Node
|
||||||
|
if len(o.ResourceName) > 0 {
|
||||||
|
node, err := o.Client.Nodes().Get(o.ResourceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nodes = append(nodes, *node)
|
||||||
|
} else {
|
||||||
|
nodeList, err := o.Client.Nodes().List(api.ListOptions{
|
||||||
|
LabelSelector: selector,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nodes = append(nodes, nodeList.Items...)
|
||||||
|
}
|
||||||
|
|
||||||
|
allocatable := make(map[string]api.ResourceList)
|
||||||
|
|
||||||
|
for _, n := range nodes {
|
||||||
|
allocatable[n.Name] = n.Status.Allocatable
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.Printer.PrintNodeMetrics(metrics, allocatable)
|
||||||
}
|
}
|
||||||
|
@ -23,31 +23,40 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apiPrefix = "api"
|
||||||
|
apiVersion = "v1"
|
||||||
|
)
|
||||||
|
|
||||||
func TestTopNodeAllMetrics(t *testing.T) {
|
func TestTopNodeAllMetrics(t *testing.T) {
|
||||||
initTestErrorHandler(t)
|
initTestErrorHandler(t)
|
||||||
metrics := testNodeMetricsData()
|
metrics, nodes := testNodeMetricsData()
|
||||||
expectedPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsApiVersion)
|
expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsApiVersion)
|
||||||
|
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||||
|
|
||||||
f, tf, _, ns := NewAPIFactory()
|
f, tf, codec, ns := NewAPIFactory()
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == expectedPath && m == "GET":
|
case p == expectedMetricsPath && m == "GET":
|
||||||
body, err := marshallBody(metrics)
|
body, err := marshallBody(metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
||||||
|
case p == expectedNodePath && m == "GET":
|
||||||
|
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, nodes)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
|
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -70,12 +79,14 @@ func TestTopNodeAllMetrics(t *testing.T) {
|
|||||||
|
|
||||||
func TestTopNodeWithNameMetrics(t *testing.T) {
|
func TestTopNodeWithNameMetrics(t *testing.T) {
|
||||||
initTestErrorHandler(t)
|
initTestErrorHandler(t)
|
||||||
metrics := testNodeMetricsData()
|
metrics, nodes := testNodeMetricsData()
|
||||||
expectedMetrics := metrics[0]
|
expectedMetrics := metrics[0]
|
||||||
|
expectedNode := &nodes.Items[0]
|
||||||
nonExpectedMetrics := metrics[1:]
|
nonExpectedMetrics := metrics[1:]
|
||||||
expectedPath := fmt.Sprintf("%s/%s/nodes/%s", baseMetricsAddress, metricsApiVersion, expectedMetrics.Name)
|
expectedPath := fmt.Sprintf("%s/%s/nodes/%s", baseMetricsAddress, metricsApiVersion, expectedMetrics.Name)
|
||||||
|
expectedNodePath := fmt.Sprintf("/%s/%s/nodes/%s", apiPrefix, apiVersion, expectedMetrics.Name)
|
||||||
|
|
||||||
f, tf, _, ns := NewAPIFactory()
|
f, tf, codec, ns := NewAPIFactory()
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -87,6 +98,8 @@ func TestTopNodeWithNameMetrics(t *testing.T) {
|
|||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
||||||
|
case p == expectedNodePath && m == "GET":
|
||||||
|
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, expectedNode)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
|
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -114,14 +127,19 @@ func TestTopNodeWithNameMetrics(t *testing.T) {
|
|||||||
|
|
||||||
func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
|
func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
|
||||||
initTestErrorHandler(t)
|
initTestErrorHandler(t)
|
||||||
metrics := testNodeMetricsData()
|
metrics, nodes := testNodeMetricsData()
|
||||||
expectedMetrics := metrics[0:1]
|
expectedMetrics := metrics[0:1]
|
||||||
|
expectedNodes := &api.NodeList{
|
||||||
|
ListMeta: nodes.ListMeta,
|
||||||
|
Items: nodes.Items[0:1],
|
||||||
|
}
|
||||||
nonExpectedMetrics := metrics[1:]
|
nonExpectedMetrics := metrics[1:]
|
||||||
label := "key=value"
|
label := "key=value"
|
||||||
expectedPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsApiVersion)
|
expectedPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsApiVersion)
|
||||||
expectedQuery := fmt.Sprintf("labelSelector=%s", url.QueryEscape(label))
|
expectedQuery := fmt.Sprintf("labelSelector=%s", url.QueryEscape(label))
|
||||||
|
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
|
||||||
|
|
||||||
f, tf, _, ns := NewAPIFactory()
|
f, tf, codec, ns := NewAPIFactory()
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -133,6 +151,8 @@ func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
|
|||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
||||||
|
case p == expectedNodePath && m == "GET":
|
||||||
|
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, expectedNodes)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
|
t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -24,9 +24,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1"
|
metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
api "k8s.io/kubernetes/pkg/api/v1"
|
v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,93 +57,121 @@ func marshallBody(metrics interface{}) (io.ReadCloser, error) {
|
|||||||
return ioutil.NopCloser(bytes.NewReader(result)), nil
|
return ioutil.NopCloser(bytes.NewReader(result)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNodeMetricsData() []metrics_api.NodeMetrics {
|
func testNodeMetricsData() ([]metrics_api.NodeMetrics, *api.NodeList) {
|
||||||
return []metrics_api.NodeMetrics{
|
metrics := []metrics_api.NodeMetrics{
|
||||||
{
|
{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "node1", ResourceVersion: "10"},
|
ObjectMeta: v1.ObjectMeta{Name: "node1", ResourceVersion: "10"},
|
||||||
Window: unversioned.Duration{Duration: time.Minute},
|
Window: unversioned.Duration{Duration: time.Minute},
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "node2", ResourceVersion: "11"},
|
ObjectMeta: v1.ObjectMeta{Name: "node2", ResourceVersion: "11"},
|
||||||
Window: unversioned.Duration{Duration: time.Minute},
|
Window: unversioned.Duration{Duration: time.Minute},
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(5, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(5, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(7*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(7*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
nodes := &api.NodeList{
|
||||||
|
ListMeta: unversioned.ListMeta{
|
||||||
|
ResourceVersion: "15",
|
||||||
|
},
|
||||||
|
Items: []api.Node{
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "node1", ResourceVersion: "10"},
|
||||||
|
Status: api.NodeStatus{
|
||||||
|
Allocatable: api.ResourceList{
|
||||||
|
api.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI),
|
||||||
|
api.ResourceMemory: *resource.NewQuantity(20*(1024*1024), resource.DecimalSI),
|
||||||
|
api.ResourceStorage: *resource.NewQuantity(30*(1024*1024), resource.DecimalSI),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "node2", ResourceVersion: "11"},
|
||||||
|
Status: api.NodeStatus{
|
||||||
|
Allocatable: api.ResourceList{
|
||||||
|
api.ResourceCPU: *resource.NewMilliQuantity(50, resource.DecimalSI),
|
||||||
|
api.ResourceMemory: *resource.NewQuantity(60*(1024*1024), resource.DecimalSI),
|
||||||
|
api.ResourceStorage: *resource.NewQuantity(70*(1024*1024), resource.DecimalSI),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return metrics, nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPodMetricsData() []metrics_api.PodMetrics {
|
func testPodMetricsData() []metrics_api.PodMetrics {
|
||||||
return []metrics_api.PodMetrics{
|
return []metrics_api.PodMetrics{
|
||||||
{
|
{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
|
ObjectMeta: v1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
|
||||||
Window: unversioned.Duration{Duration: time.Minute},
|
Window: unversioned.Duration{Duration: time.Minute},
|
||||||
Containers: []metrics_api.ContainerMetrics{
|
Containers: []metrics_api.ContainerMetrics{
|
||||||
{
|
{
|
||||||
Name: "container1-1",
|
Name: "container1-1",
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "container1-2",
|
Name: "container1-2",
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11"},
|
ObjectMeta: v1.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11"},
|
||||||
Window: unversioned.Duration{Duration: time.Minute},
|
Window: unversioned.Duration{Duration: time.Minute},
|
||||||
Containers: []metrics_api.ContainerMetrics{
|
Containers: []metrics_api.ContainerMetrics{
|
||||||
{
|
{
|
||||||
Name: "container2-1",
|
Name: "container2-1",
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "container2-2",
|
Name: "container2-2",
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(11*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(11*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(12*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(12*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "container2-3",
|
Name: "container2-3",
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(13, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(13, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(14*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(14*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(15*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(15*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"},
|
ObjectMeta: v1.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"},
|
||||||
Window: unversioned.Duration{Duration: time.Minute},
|
Window: unversioned.Duration{Duration: time.Minute},
|
||||||
Containers: []metrics_api.ContainerMetrics{
|
Containers: []metrics_api.ContainerMetrics{
|
||||||
{
|
{
|
||||||
Name: "container3-1",
|
Name: "container3-1",
|
||||||
Usage: api.ResourceList{
|
Usage: v1.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
|
v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
|
||||||
api.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
|
v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
|
||||||
api.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
|
v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -45,7 +45,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HeapsterMetricsClient struct {
|
type HeapsterMetricsClient struct {
|
||||||
Client *client.Client
|
*client.Client
|
||||||
HeapsterNamespace string
|
HeapsterNamespace string
|
||||||
HeapsterScheme string
|
HeapsterScheme string
|
||||||
HeapsterService string
|
HeapsterService string
|
||||||
@ -66,7 +66,7 @@ func DefaultHeapsterMetricsClient(client *client.Client) *HeapsterMetricsClient
|
|||||||
return NewHeapsterMetricsClient(client, DefaultHeapsterNamespace, DefaultHeapsterScheme, DefaultHeapsterService, DefaultHeapsterPort)
|
return NewHeapsterMetricsClient(client, DefaultHeapsterNamespace, DefaultHeapsterScheme, DefaultHeapsterService, DefaultHeapsterPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PodMetricsUrl(namespace string, name string) (string, error) {
|
func podMetricsUrl(namespace string, name string) (string, error) {
|
||||||
errs := validation.ValidateNamespaceName(namespace, false)
|
errs := validation.ValidateNamespaceName(namespace, false)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
message := fmt.Sprintf("invalid namespace: %s - %v", namespace, errs)
|
message := fmt.Sprintf("invalid namespace: %s - %v", namespace, errs)
|
||||||
@ -82,7 +82,7 @@ func PodMetricsUrl(namespace string, name string) (string, error) {
|
|||||||
return fmt.Sprintf("%s/namespaces/%s/pods/%s", MetricsRoot, namespace, name), nil
|
return fmt.Sprintf("%s/namespaces/%s/pods/%s", MetricsRoot, namespace, name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NodeMetricsUrl(name string) (string, error) {
|
func nodeMetricsUrl(name string) (string, error) {
|
||||||
if len(name) > 0 {
|
if len(name) > 0 {
|
||||||
errs := validation.ValidateNodeName(name, false)
|
errs := validation.ValidateNodeName(name, false)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
@ -95,7 +95,7 @@ func NodeMetricsUrl(name string) (string, error) {
|
|||||||
|
|
||||||
func (cli *HeapsterMetricsClient) GetNodeMetrics(nodeName string, selector string) ([]metrics_api.NodeMetrics, error) {
|
func (cli *HeapsterMetricsClient) GetNodeMetrics(nodeName string, selector string) ([]metrics_api.NodeMetrics, error) {
|
||||||
params := map[string]string{"labelSelector": selector}
|
params := map[string]string{"labelSelector": selector}
|
||||||
path, err := NodeMetricsUrl(nodeName)
|
path, err := nodeMetricsUrl(nodeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []metrics_api.NodeMetrics{}, err
|
return []metrics_api.NodeMetrics{}, err
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ func (cli *HeapsterMetricsClient) GetPodMetrics(namespace string, podName string
|
|||||||
params := map[string]string{"labelSelector": selector}
|
params := map[string]string{"labelSelector": selector}
|
||||||
allMetrics := make([]metrics_api.PodMetrics, 0)
|
allMetrics := make([]metrics_api.PodMetrics, 0)
|
||||||
for _, ns := range namespaces {
|
for _, ns := range namespaces {
|
||||||
path, err := PodMetricsUrl(ns, podName)
|
path, err := podMetricsUrl(ns, podName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []metrics_api.PodMetrics{}, err
|
return []metrics_api.PodMetrics{}, err
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ func (cli *HeapsterMetricsClient) GetPodMetrics(namespace string, podName string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetHeapsterMetrics(cli *HeapsterMetricsClient, path string, params map[string]string) ([]byte, error) {
|
func GetHeapsterMetrics(cli *HeapsterMetricsClient, path string, params map[string]string) ([]byte, error) {
|
||||||
return cli.Client.Services(cli.HeapsterNamespace).
|
return cli.Services(cli.HeapsterNamespace).
|
||||||
ProxyGet(cli.HeapsterScheme, cli.HeapsterService, cli.HeapsterPort, path, params).
|
ProxyGet(cli.HeapsterScheme, cli.HeapsterService, cli.HeapsterPort, path, params).
|
||||||
DoRaw()
|
DoRaw()
|
||||||
}
|
}
|
||||||
|
@ -19,30 +19,28 @@ package metricsutil
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
|
||||||
|
|
||||||
metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1"
|
metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl"
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
MeasuredResources = []v1.ResourceName{
|
MeasuredResources = []api.ResourceName{
|
||||||
v1.ResourceCPU,
|
api.ResourceCPU,
|
||||||
v1.ResourceMemory,
|
api.ResourceMemory,
|
||||||
v1.ResourceStorage,
|
|
||||||
}
|
}
|
||||||
NodeColumns = []string{"NAME", "CPU (cores)", "MEMORY (bytes)", "STORAGE (bytes)", "TIMESTAMP"}
|
NodeColumns = []string{"NAME", "CPU(cores)", "CPU%", "MEMORY(bytes)", "MEMORY%"}
|
||||||
PodColumns = []string{"NAME", "CPU (cores)", "MEMORY (bytes)", "STORAGE (bytes)", "TIMESTAMP"}
|
PodColumns = []string{"NAME", "CPU(cores)", "MEMORY(bytes)"}
|
||||||
NamespaceColumn = "NAMESPACE"
|
NamespaceColumn = "NAMESPACE"
|
||||||
PodColumn = "POD"
|
PodColumn = "POD"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResourceMetricsInfo struct {
|
type ResourceMetricsInfo struct {
|
||||||
Name string
|
Name string
|
||||||
Metrics v1.ResourceList
|
Metrics api.ResourceList
|
||||||
Timestamp string
|
Available api.ResourceList
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopCmdPrinter struct {
|
type TopCmdPrinter struct {
|
||||||
@ -53,7 +51,7 @@ func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
|
|||||||
return &TopCmdPrinter{out: out}
|
return &TopCmdPrinter{out: out}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metrics_api.NodeMetrics) error {
|
func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metrics_api.NodeMetrics, availableResources map[string]api.ResourceList) error {
|
||||||
if len(metrics) == 0 {
|
if len(metrics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -61,11 +59,16 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metrics_api.NodeMetrics
|
|||||||
defer w.Flush()
|
defer w.Flush()
|
||||||
|
|
||||||
printColumnNames(w, NodeColumns)
|
printColumnNames(w, NodeColumns)
|
||||||
|
var usage api.ResourceList
|
||||||
for _, m := range metrics {
|
for _, m := range metrics {
|
||||||
|
err := api.Scheme.Convert(&m.Usage, &usage, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
printMetricsLine(w, &ResourceMetricsInfo{
|
printMetricsLine(w, &ResourceMetricsInfo{
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
Metrics: m.Usage,
|
Metrics: usage,
|
||||||
Timestamp: m.Timestamp.Time.Format(time.RFC1123Z),
|
Available: availableResources[m.Name],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -86,7 +89,10 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metrics_api.PodMetrics,
|
|||||||
}
|
}
|
||||||
printColumnNames(w, PodColumns)
|
printColumnNames(w, PodColumns)
|
||||||
for _, m := range metrics {
|
for _, m := range metrics {
|
||||||
printSinglePodMetrics(w, &m, printContainers, withNamespace)
|
err := printSinglePodMetrics(w, &m, printContainers, withNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -98,18 +104,23 @@ func printColumnNames(out io.Writer, names []string) {
|
|||||||
fmt.Fprint(out, "\n")
|
fmt.Fprint(out, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSinglePodMetrics(out io.Writer, m *metrics_api.PodMetrics, printContainersOnly bool, withNamespace bool) {
|
func printSinglePodMetrics(out io.Writer, m *metrics_api.PodMetrics, printContainersOnly bool, withNamespace bool) error {
|
||||||
containers := make(map[string]v1.ResourceList)
|
containers := make(map[string]api.ResourceList)
|
||||||
podMetrics := make(v1.ResourceList)
|
podMetrics := make(api.ResourceList)
|
||||||
for _, res := range MeasuredResources {
|
for _, res := range MeasuredResources {
|
||||||
podMetrics[res], _ = resource.ParseQuantity("0")
|
podMetrics[res], _ = resource.ParseQuantity("0")
|
||||||
}
|
}
|
||||||
|
var usage api.ResourceList
|
||||||
for _, c := range m.Containers {
|
for _, c := range m.Containers {
|
||||||
containers[c.Name] = c.Usage
|
err := api.Scheme.Convert(&c.Usage, &usage, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containers[c.Name] = usage
|
||||||
if !printContainersOnly {
|
if !printContainersOnly {
|
||||||
for _, res := range MeasuredResources {
|
for _, res := range MeasuredResources {
|
||||||
quantity := podMetrics[res]
|
quantity := podMetrics[res]
|
||||||
quantity.Add(c.Usage[res])
|
quantity.Add(usage[res])
|
||||||
podMetrics[res] = quantity
|
podMetrics[res] = quantity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +134,7 @@ func printSinglePodMetrics(out io.Writer, m *metrics_api.PodMetrics, printContai
|
|||||||
printMetricsLine(out, &ResourceMetricsInfo{
|
printMetricsLine(out, &ResourceMetricsInfo{
|
||||||
Name: contName,
|
Name: contName,
|
||||||
Metrics: containers[contName],
|
Metrics: containers[contName],
|
||||||
Timestamp: m.Timestamp.Time.Format(time.RFC1123Z),
|
Available: api.ResourceList{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -133,15 +144,15 @@ func printSinglePodMetrics(out io.Writer, m *metrics_api.PodMetrics, printContai
|
|||||||
printMetricsLine(out, &ResourceMetricsInfo{
|
printMetricsLine(out, &ResourceMetricsInfo{
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
Metrics: podMetrics,
|
Metrics: podMetrics,
|
||||||
Timestamp: m.Timestamp.Time.Format(time.RFC1123Z),
|
Available: api.ResourceList{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {
|
func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {
|
||||||
printValue(out, metrics.Name)
|
printValue(out, metrics.Name)
|
||||||
printAllResourceUsages(out, metrics.Metrics)
|
printAllResourceUsages(out, metrics)
|
||||||
printValue(out, metrics.Timestamp)
|
|
||||||
fmt.Fprint(out, "\n")
|
fmt.Fprint(out, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,23 +160,24 @@ func printValue(out io.Writer, value interface{}) {
|
|||||||
fmt.Fprintf(out, "%v\t", value)
|
fmt.Fprintf(out, "%v\t", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printAllResourceUsages(out io.Writer, usage v1.ResourceList) {
|
func printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo) {
|
||||||
for _, res := range MeasuredResources {
|
for _, res := range MeasuredResources {
|
||||||
quantity := usage[res]
|
quantity := metrics.Metrics[res]
|
||||||
printSingleResourceUsage(out, res, quantity)
|
printSingleResourceUsage(out, res, quantity)
|
||||||
fmt.Fprint(out, "\t")
|
fmt.Fprint(out, "\t")
|
||||||
|
if available, found := metrics.Available[res]; found {
|
||||||
|
fraction := float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
|
||||||
|
fmt.Fprintf(out, "%d%%\t", int64(fraction))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quantity resource.Quantity) {
|
func printSingleResourceUsage(out io.Writer, resourceType api.ResourceName, quantity resource.Quantity) {
|
||||||
switch resourceType {
|
switch resourceType {
|
||||||
case v1.ResourceCPU:
|
case api.ResourceCPU:
|
||||||
fmt.Fprintf(out, "%vm", quantity.MilliValue())
|
fmt.Fprintf(out, "%vm", quantity.MilliValue())
|
||||||
case v1.ResourceMemory:
|
case api.ResourceMemory:
|
||||||
fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024))
|
fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024))
|
||||||
case v1.ResourceStorage:
|
|
||||||
// TODO: Change it after storage metrics collection is finished.
|
|
||||||
fmt.Fprint(out, "-")
|
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(out, "%v", quantity.Value())
|
fmt.Fprintf(out, "%v", quantity.Value())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user