Repository: vmware-tanzu/octant
Pull request #452 (2019-11-29 20:02:16)

Create workload viewer
Create a workload viewer that shows the status of workloads running in a cluster. This will be the new home page for Octant.

 Workload shows status for pods in a namespaced grouped by owner. If the cluster
 supports the pod metrics API, it will also include CPU and memory metrics
 for the group of pods.

![dark](https://user-images.githubusercontent.com/240/70379887-02bfb000-1901-11ea-82d3-79d813056d59.png)
![light](https://user-images.githubusercontent.com/240/70379888-06533700-1901-11ea-8d5e-98e821a44d0c.png)
![No metrics server](https://user-images.githubusercontent.com/240/70853626-7cfeb000-1e7e-11ea-89cb-c58856f559b8.png)



wwitzel3: I started to look at this today but couldn't give my full attention because I distracted with #135 stuff. I'll give it a full dedicated code review tomorrow. Sorry for the delay in getting to this.
wwitzel3: When I was testing this it worked great on GKE (1.13), but was not working on a KIND (1.15) cluster, using the exact same YAML. (example/resources/deployment.yaml)
bryanl: > When I was testing this it worked great on GKE (1.13), but was not working on a KIND (1.15) cluster, using the exact same YAML. (example/resources/deployment.yaml)

Do you have a screenshot?
wwitzel3: ![no-workload-kind](https://user-images.githubusercontent.com/27317/70810121-2ff0e000-1d91-11ea-8547-83889653f9ef.gif)

No errors in the web console (besides preventDefault) and no errors in the terminal.

Patch:
diff --git a/build.go b/build.go
index 05e35817..15d5a2ef 100644
--- a/build.go
+++ b/build.go
@@ -60,7 +60,6 @@ func main() {
 		case "vet":
 			vet()
 		case "test":
-			generate()
 			test()
 		case "build":
 			build()
@@ -147,7 +146,7 @@ func build() {
 }
 
 func runDev() {
-	runCmd("build/octant", nil, "--enable-feature-applications=true")
+	runCmd("build/octant", nil)
 }
 
 func test() {
diff --git a/changelogs/unreleased/480-bryanl b/changelogs/unreleased/480-bryanl
new file mode 100644
index 00000000..5421bdf3
--- /dev/null
+++ b/changelogs/unreleased/480-bryanl
@@ -0,0 +1,2 @@
+Create workload module. This commit creates a new octant module, "workload".  Workload shows status for pods in a namespaced grouped by owner. If the cluster supports the pod metrics API, it will also include CPU and memory metrics for the group of pods.
+
diff --git a/cmd/octant-sample-plugin/main.go b/cmd/octant-sample-plugin/main.go
index 079dd7e8..0d1a7ce7 100644
--- a/cmd/octant-sample-plugin/main.go
+++ b/cmd/octant-sample-plugin/main.go
@@ -113,7 +113,7 @@ func handlePrint(request *service.PrintRequest) (plugin.PrintResponse, error) {
 
 	// Octant has a component library that can be used to build content for a plugin.
 	// In this case, the plugin is creating a card.
-	podCard := component.NewCard(fmt.Sprintf("Extra Output for %s", u.GetName()))
+	podCard := component.NewCard(component.TitleFromString(fmt.Sprintf("Extra Output for %s", u.GetName())))
 	podCard.SetBody(component.NewMarkdownText("This output was generated from _octant-sample-plugin_"))
 
 	msg := fmt.Sprintf("update from plugin at %s", time.Now().Format(time.RFC3339))
@@ -172,7 +172,7 @@ func handleNavigation(request *service.NavigationRequest) (navigation.Navigation
 func initRoutes(router *service.Router) {
 	gen := func(name, accessor, requestPath string) component.Component {
 		cardBody := component.NewText(fmt.Sprintf("hello from plugin: path %s", requestPath))
-		card := component.NewCard(fmt.Sprintf("My Card - %s", name))
+		card := component.NewCard(component.TitleFromString(fmt.Sprintf("My Card - %s", name)))
 		card.SetBody(cardBody)
 		cardList := component.NewCardList(name)
 		cardList.AddCard(*card)
diff --git a/go.mod b/go.mod
index b08ea287..c52994f0 100644
--- a/go.mod
+++ b/go.mod
@@ -36,9 +36,7 @@ require (
 	github.com/stretchr/testify v1.3.0
 	go.opencensus.io v0.22.1
 	go.uber.org/zap v1.10.0
-	golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 // indirect
 	golang.org/x/sync v0.0.0-20190423024810-112230192c58
-	golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 // indirect
 	google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 // indirect
 	google.golang.org/grpc v1.23.0
 	gopkg.in/inf.v0 v0.9.1 // indirect
@@ -48,6 +46,7 @@ require (
 	k8s.io/client-go v0.0.0-20191016230210-14c42cd304d9
 	k8s.io/klog v1.0.0
 	k8s.io/kubernetes v1.14.0
+	k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e
 	k8s.io/utils v0.0.0-20191010214722-8d271d903fe4
 )
 
diff --git a/go.sum b/go.sum
index 1e9ca944..b64d84f7 100644
--- a/go.sum
+++ b/go.sum
@@ -7,6 +7,7 @@ github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtz
 github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
 github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
 github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
@@ -14,7 +15,9 @@ github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtix
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
@@ -57,6 +60,7 @@ github.com/elazarl/goproxy v0.0.0-20190703090003-6125c262ffb0/go.mod h1:/Zj4wYkg
 github.com/elazarl/goproxy/ext v0.0.0-20190703090003-6125c262ffb0 h1:ht1Fo9uxmemH6/Or11+OosQxf6UKeauPI6Ure8KVuWw=
 github.com/elazarl/goproxy/ext v0.0.0-20190703090003-6125c262ffb0/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -69,9 +73,13 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
+github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
@@ -166,11 +174,13 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
@@ -196,11 +206,13 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
@@ -231,6 +243,7 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
 github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
@@ -256,6 +269,7 @@ github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
@@ -286,10 +300,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -304,6 +322,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJV
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
@@ -352,14 +371,21 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
 golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.3.2 h1:iTp+3yyl/KOtxa/d1/JUE0GGSoR6FuW5udver22iwpw=
 google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
@@ -408,21 +434,26 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 k8s.io/api v0.0.0-20190620084959-7cf5895f2711 h1:BblVYz/wE5WtBsD/Gvu54KyBUTJMflolzc5I2DTvh50=
 k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=
+k8s.io/api v0.0.0-20191016110408-35e52d86657a/go.mod h1:/L5qH+AD540e7Cetbui1tuJeXdmNhO8jM6VkXeDdDhQ=
 k8s.io/api v0.0.0-20191016225839-816a9b7df678 h1:z/0BV/tMBIvdwZvqBH/f7TWjQX9y3dj1nMNhrSK0h/8=
 k8s.io/api v0.0.0-20191016225839-816a9b7df678/go.mod h1:LZQaT8MvVpl7Bg2lYFcQm7+Mpdxq8p1NFl3yh+5DCwY=
 k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476 h1:Ws9zfxsgV19Durts9ftyTG7TO0A/QLhmu98VqNWLiH8=
 k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
 k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719 h1:uV4S5IB5g4Nvi+TBVNf3e9L4wrirlwYJ6w88jUQxTUw=
 k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=
+k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
 k8s.io/apimachinery v0.0.0-20191016225534-b1267f8c42b4 h1:XRoHQDFMktgtZM+TTvV8nYcp2hXHhnx1E0RJOUX3t/E=
 k8s.io/apimachinery v0.0.0-20191016225534-b1267f8c42b4/go.mod h1:92mWDd8Ji2sw2157KIgino5wCxffA8KSvhW2oY4ypdw=
 k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g=
 k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k=
+k8s.io/code-generator v0.0.0-20191004115455-8e001e5d1894/go.mod h1:mJUgkl06XV4kstAnLHAIzJPVCOzVR+ZcfPIv4fUsFCY=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
 k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=
@@ -431,10 +462,17 @@ k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJD
 k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kubernetes v1.14.0 h1:6T2iAEoOYQnzQb3WvPlUkcczEEXZ7+YPlAO8olwujRw=
 k8s.io/kubernetes v1.14.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
+k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e h1:VbAmCGT95GvCZaGtW3oLhf7d2FXT+lnWiTtPE8hzPVo=
+k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e/go.mod h1:ve7/vMWeY5lEBkZf6Bt5TTbGS3b8wAxwGbdXAsufjRs=
 k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4=
 k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
 k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 h1:Gi+/O1saihwDqnlmC8Vhv1M5Sp4+rbOmK9TbsLn8ZEA=
 k8s.io/utils v0.0.0-20191010214722-8d271d903fe4/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
+modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
+modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
+modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
+modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
 sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
diff --git a/internal/gvk/gvk.go b/internal/gvk/gvk.go
index fbdacfa3..d195c826 100644
--- a/internal/gvk/gvk.go
+++ b/internal/gvk/gvk.go
@@ -30,6 +30,7 @@ var (
 	Secret                   = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
 	Service                  = schema.GroupVersionKind{Version: "v1", Kind: "Service"}
 	Pod                      = schema.GroupVersionKind{Version: "v1", Kind: "Pod"}
+	PodMetrics               = schema.GroupVersionKind{Group: "metrics.k8s.io", Version: "v1beta1", Kind: "PodMetrics"}
 	PersistentVolumeClaim    = schema.GroupVersionKind{Version: "v1", Kind: "PersistentVolumeClaim"}
 	ReplicationController    = schema.GroupVersionKind{Version: "v1", Kind: "ReplicationController"}
 	StatefulSet              = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}
diff --git a/internal/modules/workloads/home_describer.go b/internal/modules/workloads/home_describer.go
new file mode 100644
index 00000000..d7d188ba
--- /dev/null
+++ b/internal/modules/workloads/home_describer.go
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package workloads
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/vmware-tanzu/octant/internal/describer"
+	"github.com/vmware-tanzu/octant/internal/octant"
+	"github.com/vmware-tanzu/octant/pkg/view/component"
+)
+
+// HomeDescriberOption is an option for configuring home describer.
+type HomeDescriberOption func(hd *HomeDescriber)
+
+// HomeDescriber describe the home page for workloads module.
+type HomeDescriber struct{}
+
+var _ describer.Describer = (*HomeDescriber)(nil)
+
+// NewHomeDescriber creates an instance of HomeDescriber.
+func NewHomeDescriber(options ...HomeDescriberOption) (*HomeDescriber, error) {
+	d := &HomeDescriber{}
+
+	for _, option := range options {
+		option(d)
+	}
+
+	return d, nil
+}
+
+// Describe creates a content response for workloads.
+func (h *HomeDescriber) Describe(ctx context.Context, namespace string, options describer.Options) (component.ContentResponse, error) {
+	pml, err := octant.NewClusterPodMetricsLoader(options.Dash.ClusterClient())
+	if err != nil {
+		return component.EmptyContentResponse, fmt.Errorf("create pod metrics loader")
+	}
+
+	loader, err := octant.NewClusterWorkloadLoader(options.Dash.ObjectStore(), pml)
+	if err != nil {
+		return component.EmptyContentResponse, fmt.Errorf("create workload loader")
+	}
+
+	collector, err := octant.NewWorkloadCardCollector(loader)
+	if err != nil {
+		return component.EmptyContentResponse, fmt.Errorf("create card collector")
+	}
+
+	cards, fullMetrics, err := collector.Collect(ctx, namespace)
+	if err != nil {
+		return component.EmptyContentResponse, fmt.Errorf("collect workload cards")
+	}
+
+	cardWidth := component.WidthHalf
+	if !fullMetrics {
+		cardWidth = component.WidthQuarter
+	}
+
+	layout := component.NewFlexLayout("Workloads")
+	section := component.FlexLayoutSection{}
+
+	for _, card := range cards {
+		section = append(section, component.FlexLayoutItem{
+			Width: cardWidth,
+			View:  card,
+		})
+	}
+
+	layout.AddSections(section)
+
+	cr := component.ContentResponse{
+		Title:      component.TitleFromString("Workloads"),
+		Components: []component.Component{layout},
+		IconName:   "",
+		IconSource: "",
+	}
+
+	return cr, nil
+}
+
+// PathFilters returns a path filter for the root path.
+func (h *HomeDescriber) PathFilters() []describer.PathFilter {
+	return []describer.PathFilter{
+		*describer.NewPathFilter("/", h),
+	}
+}
+
+// Reset is a no-op.
+func (h HomeDescriber) Reset(ctx context.Context) error {
+	return nil
+}
diff --git a/internal/modules/workloads/module.go b/internal/modules/workloads/module.go
new file mode 100644
index 00000000..5cc9e74d
--- /dev/null
+++ b/internal/modules/workloads/module.go
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package workloads
+
+import (
+	"context"
+	"fmt"
+	"path/filepath"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
+	"github.com/vmware-tanzu/octant/internal/config"
+	"github.com/vmware-tanzu/octant/internal/describer"
+	"github.com/vmware-tanzu/octant/internal/generator"
+	"github.com/vmware-tanzu/octant/internal/module"
+	"github.com/vmware-tanzu/octant/internal/octant"
+	"github.com/vmware-tanzu/octant/pkg/navigation"
+	"github.com/vmware-tanzu/octant/pkg/view/component"
+)
+
+// Options for configuring Module.
+type Options struct {
+	DashConfig config.Dash
+}
+
+// Module contains the implementation for the workloads module.
+type Module struct {
+	Options
+	pathMatcher *describer.PathMatcher
+}
+
+var _ module.Module = (*Module)(nil)
+
+// New creates an instance of Module.
+func New(ctx context.Context, options Options) (*Module, error) {
+	rootDescriber, err := NewHomeDescriber()
+	if err != nil {
+		return nil, fmt.Errorf("create home describe: %w", err)
+	}
+
+	pm := describer.NewPathMatcher("workloads")
+
+	for _, pf := range rootDescriber.PathFilters() {
+		pm.Register(ctx, pf)
+	}
+
+	m := &Module{
+		Options:     options,
+		pathMatcher: pm,
+	}
+
+	return m, nil
+}
+
+// Name returns the module name.
+func (m *Module) Name() string {
+	return "workloads"
+}
+
+// ClientRequestHandlers returns nil.
+func (m *Module) ClientRequestHandlers() []octant.ClientRequestHandler {
+	return nil
+}
+
+// Content handles content for the module.
+func (m *Module) Content(ctx context.Context, contentPath string, opts module.ContentOptions) (component.ContentResponse, error) {
+	g, err := generator.NewGenerator(m.pathMatcher, m.DashConfig)
+	if err != nil {
+		return component.EmptyContentResponse, err
+	}
+
+	return g.Generate(ctx, contentPath, generator.Options{})
+}
+
+// ContentPath returns the content path for this module.
+func (m *Module) ContentPath() string {
+	return m.Name()
+}
+
+// Navigation returns navigation entries for the module.
+func (m *Module) Navigation(ctx context.Context, namespace, root string) ([]navigation.Navigation, error) {
+	rootPath := filepath.Join(m.ContentPath(), "namespace", namespace)
+
+	rootNav := navigation.Navigation{
+		Title: "Workloads",
+		Path:  rootPath,
+	}
+
+	return []navigation.Navigation{rootNav}, nil
+}
+
+// SetNamespace is a no-op.
+func (m Module) SetNamespace(namespace string) error {
+	return nil
+}
+
+// Start is a no-op.
+func (m Module) Start() error {
+	return nil
+}
+
+// Stop is a no-op.
+func (m Module) Stop() {
+}
+
+// SetContext is a no-op.
+func (m Module) SetContext(ctx context.Context, contextName string) error {
+	return nil
+}
+
+// Generators returns nil.
+func (m Module) Generators() []octant.Generator {
+	return nil
+}
+
+// SupportedGroupVersionKind returns nil.
+func (m Module) SupportedGroupVersionKind() []schema.GroupVersionKind {
+	return nil
+}
+
+// GroupVersionKindPath return return an error as this module does not support.
+func (m Module) GroupVersionKindPath(namespace, apiVersion, kind, name string) (string, error) {
+	return "", fmt.Errorf("not supported")
+}
+
+// AddCRD is a no-op.
+func (m Module) AddCRD(ctx context.Context, crd *unstructured.Unstructured) error {
+	return nil
+}
+
+// RemoveCRD is a no-op.
+func (m Module) RemoveCRD(ctx context.Context, crd *unstructured.Unstructured) error {
+	return nil
+}
+
+// ResetCRDs is a no-op.
+func (m Module) ResetCRDs(ctx context.Context) error {
+	return nil
+}
diff --git a/internal/octant/fake/mock_pod_metrics_crud.go b/internal/octant/fake/mock_pod_metrics_crud.go
new file mode 100644
index 00000000..22d2caba
--- /dev/null
+++ b/internal/octant/fake/mock_pod_metrics_crud.go
@@ -0,0 +1,49 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/vmware-tanzu/octant/internal/octant (interfaces: PodMetricsCRUD)
+
+// Package fake is a generated GoMock package.
+package fake
+
+import (
+	gomock "github.com/golang/mock/gomock"
+	unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	reflect "reflect"
+)
+
+// MockPodMetricsCRUD is a mock of PodMetricsCRUD interface
+type MockPodMetricsCRUD struct {
+	ctrl     *gomock.Controller
+	recorder *MockPodMetricsCRUDMockRecorder
+}
+
+// MockPodMetricsCRUDMockRecorder is the mock recorder for MockPodMetricsCRUD
+type MockPodMetricsCRUDMockRecorder struct {
+	mock *MockPodMetricsCRUD
+}
+
+// NewMockPodMetricsCRUD creates a new mock instance
+func NewMockPodMetricsCRUD(ctrl *gomock.Controller) *MockPodMetricsCRUD {
+	mock := &MockPodMetricsCRUD{ctrl: ctrl}
+	mock.recorder = &MockPodMetricsCRUDMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockPodMetricsCRUD) EXPECT() *MockPodMetricsCRUDMockRecorder {
+	return m.recorder
+}
+
+// Get mocks base method
+func (m *MockPodMetricsCRUD) Get(arg0, arg1 string) (*unstructured.Unstructured, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Get", arg0, arg1)
+	ret0, _ := ret[0].(*unstructured.Unstructured)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Get indicates an expected call of Get
+func (mr *MockPodMetricsCRUDMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockPodMetricsCRUD)(nil).Get), arg0, arg1)
+}
diff --git a/internal/octant/fake/mock_pod_metrics_loader.go b/internal/octant/fake/mock_pod_metrics_loader.go
new file mode 100644
index 00000000..ada76772
--- /dev/null
+++ b/internal/octant/fake/mock_pod_metrics_loader.go
@@ -0,0 +1,64 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/vmware-tanzu/octant/internal/octant (interfaces: PodMetricsLoader)
+
+// Package fake is a generated GoMock package.
+package fake
+
+import (
+	gomock "github.com/golang/mock/gomock"
+	unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	reflect "reflect"
+)
+
+// MockPodMetricsLoader is a mock of PodMetricsLoader interface
+type MockPodMetricsLoader struct {
+	ctrl     *gomock.Controller
+	recorder *MockPodMetricsLoaderMockRecorder
+}
+
+// MockPodMetricsLoaderMockRecorder is the mock recorder for MockPodMetricsLoader
+type MockPodMetricsLoaderMockRecorder struct {
+	mock *MockPodMetricsLoader
+}
+
+// NewMockPodMetricsLoader creates a new mock instance
+func NewMockPodMetricsLoader(ctrl *gomock.Controller) *MockPodMetricsLoader {
+	mock := &MockPodMetricsLoader{ctrl: ctrl}
+	mock.recorder = &MockPodMetricsLoaderMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockPodMetricsLoader) EXPECT() *MockPodMetricsLoaderMockRecorder {
+	return m.recorder
+}
+
+// Load mocks base method
+func (m *MockPodMetricsLoader) Load(arg0, arg1 string) (*unstructured.Unstructured, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Load", arg0, arg1)
+	ret0, _ := ret[0].(*unstructured.Unstructured)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Load indicates an expected call of Load
+func (mr *MockPodMetricsLoaderMockRecorder) Load(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Load", reflect.TypeOf((*MockPodMetricsLoader)(nil).Load), arg0, arg1)
+}
+
+// SupportsMetrics mocks base method
+func (m *MockPodMetricsLoader) SupportsMetrics() (bool, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SupportsMetrics")
+	ret0, _ := ret[0].(bool)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SupportsMetrics indicates an expected call of SupportsMetrics
+func (mr *MockPodMetricsLoaderMockRecorder) SupportsMetrics() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportsMetrics", reflect.TypeOf((*MockPodMetricsLoader)(nil).SupportsMetrics))
+}
diff --git a/internal/octant/pod_metrics_loader.go b/internal/octant/pod_metrics_loader.go
new file mode 100644
index 00000000..d6b4f178
--- /dev/null
+++ b/internal/octant/pod_metrics_loader.go
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package octant
+
+import (
+	"fmt"
+	"sync"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
+	"github.com/vmware-tanzu/octant/internal/cluster"
+	"github.com/vmware-tanzu/octant/internal/gvk"
+)
+
+//go:generate mockgen -destination=./fake/mock_pod_metrics_loader.go -package=fake github.com/vmware-tanzu/octant/internal/octant PodMetricsLoader
+//go:generate mockgen -destination=./fake/mock_pod_metrics_crud.go -package=fake github.com/vmware-tanzu/octant/internal/octant PodMetricsCRUD
+
+// PodMetricsCRUD contains CRUD methods for accessing pod metrics.
+type PodMetricsCRUD interface {
+	// Get returns pod metrics for a pod.
+	Get(namespace, name string) (*unstructured.Unstructured, error)
+}
+
+type clusterPodMetricsCRUD struct {
+	clusterClient cluster.ClientInterface
+}
+
+var _ PodMetricsCRUD = (*clusterPodMetricsCRUD)(nil)
+
+func newClusterPodMetricsCRUD(clusterClient cluster.ClientInterface) (*clusterPodMetricsCRUD, error) {
+	if clusterClient == nil {
+		return nil, fmt.Errorf("cluster client is nil")
+	}
+
+	return &clusterPodMetricsCRUD{clusterClient: clusterClient}, nil
+}
+
+func (c *clusterPodMetricsCRUD) Get(namespace, name string) (*unstructured.Unstructured, error) {
+	client, err := c.clusterClient.DynamicClient()
+	if err != nil {
+		return nil, fmt.Errorf("get dynamic client: %w", err)
+	}
+
+	options := metav1.GetOptions{}
+	object, err := client.Resource(PodMetricsResource).Namespace(namespace).Get(name, options)
+	if err != nil {
+		return nil, fmt.Errorf("get pod metrics: %w", err)
+	}
+
+	return object, nil
+}
+
+type noPodMetricsSupport interface {
+	NoPodMetricsSupport() bool
+}
+
+// NoPodMetricsErr is an error signifying a cluster does not have pod metrics support.
+type NoPodMetricsErr struct{}
+
+var _ noPodMetricsSupport = (*NoPodMetricsErr)(nil)
+var _ error = (*NoPodMetricsErr)(nil)
+
+func (e *NoPodMetricsErr) NoPodMetricsSupport() bool {
+	return true
+}
+
+func (e *NoPodMetricsErr) Error() string {
+	return "pod metrics are not supported by this cluster"
+}
+
+// IsPodMetricsNotSupported returns true if error is pod metrics not supported.
+func IsPodMetricsNotSupported(err error) bool {
+	e, ok := err.(noPodMetricsSupport)
+	return ok && e.NoPodMetricsSupport()
+}
+
+// PodMetricsLoader loads metrics for a pod.
+type PodMetricsLoader interface {
+	// Load loads metrics for a pod given namespace and a name.
+	Load(namespace, name string) (*unstructured.Unstructured, error)
+	// SupportsMetrics returns true if the cluster has metrics support.
+	SupportsMetrics() (bool, error)
+}
+
+// ClusterPodMetricsLoaderOption is an option for configuring ClusterPodMetricsLoader.
+type ClusterPodMetricsLoaderOption func(loader *ClusterPodMetricsLoader)
+
+// ClusterPodMetricsLoader loads metrics for a pod using a cluster client.
+type ClusterPodMetricsLoader struct {
+	PodMetricsCRUD PodMetricsCRUD
+
+	clusterClient cluster.ClientInterface
+	supportsOnce  sync.Once
+
+	hasPodMetricsSupport bool
+}
+
+var _ PodMetricsLoader = (*ClusterPodMetricsLoader)(nil)
+
+// NewClusterPodMetricsLoader creates an instance of ClusterPodMetricsLoader.
+func NewClusterPodMetricsLoader(clusterClient cluster.ClientInterface, options ...ClusterPodMetricsLoaderOption) (*ClusterPodMetricsLoader, error) {
+	if clusterClient == nil {
+		return nil, fmt.Errorf("cluster client is nil")
+	}
+
+	pml := &ClusterPodMetricsLoader{
+		clusterClient: clusterClient,
+		supportsOnce:  sync.Once{},
+	}
+
+	for _, option := range options {
+		option(pml)
+	}
+
+	if pml.PodMetricsCRUD == nil {
+		pmc, err := newClusterPodMetricsCRUD(clusterClient)
+		if err != nil {
+			return nil, fmt.Errorf("create pod metrics CRUD client: %w", err)
+		}
+		pml.PodMetricsCRUD = pmc
+	}
+
+	return pml, nil
+}
+
+var (
+	// PodMetricsResource is resource for pod metrics.
+	PodMetricsResource = schema.GroupVersionResource{Group: "metrics.k8s.io", Version: "v1beta1", Resource: "pods"}
+)
+
+// Load loads metrics for a pod given namespace and a name.
+func (ml *ClusterPodMetricsLoader) Load(namespace, name string) (*unstructured.Unstructured, error) {
+	return ml.PodMetricsCRUD.Get(namespace, name)
+}
+
+func (ml *ClusterPodMetricsLoader) SupportsMetrics() (bool, error) {
+	var sErr error
+	ml.supportsOnce.Do(func() {
+		discovery, err := ml.clusterClient.DiscoveryClient()
+		if err != nil {
+			sErr = fmt.Errorf("get discovery cluster: %w", err)
+			return
+		}
+
+		lists, err := discovery.ServerPreferredNamespacedResources()
+		if err != nil {
+			sErr = fmt.Errorf("get preferred namespaced resources: %w", err)
+			return
+		}
+
+		podMetricString := fmt.Sprintf("%s %s", gvk.PodMetrics.GroupVersion().String(), gvk.PodMetrics.Kind)
+
+		for _, list := range lists {
+			for i := range list.APIResources {
+				s := fmt.Sprintf("%s %s", list.GroupVersion, list.APIResources[i].Kind)
+
+				if s == podMetricString {
+					ml.hasPodMetricsSupport = true
+				}
+			}
+		}
+	})
+
+	if sErr != nil {
+		return false, sErr
+	}
+
+	return ml.hasPodMetricsSupport, nil
+}
diff --git a/internal/octant/workload.go b/internal/octant/workload.go
new file mode 100644
index 00000000..26756bbc
--- /dev/null
+++ b/internal/octant/workload.go
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package octant
+
+import (
+	"context"
+	"fmt"
+	"sort"
+	"sync"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/vmware-tanzu/octant/internal/objectstatus"
+	"github.com/vmware-tanzu/octant/pkg/store"
+	"github.com/vmware-tanzu/octant/pkg/view/component"
+)
+
+const (
+	// WorkloadStatusColorOK is the color for ok workload status.
+	WorkloadStatusColorOK = "#60b515"
+	// WorkloadStatusColorWarning is the color for warning workload status.
+	WorkloadStatusColorWarning = "#f57600"
+	// WorkloadStatusColorError is the color for error workload status.
+	WorkloadStatusColorError = "#e12200"
+)
+
+// PodWithMetric combines a pod and resource list into a single type.
+type PodWithMetric struct {
+	Pod          *unstructured.Unstructured
+	ResourceList corev1.ResourceList
+}
+
+// Workload is a workload.
+type Workload struct {
+	// IconName is the name of the icon for this workload.
+	IconName string
+	// Name is the name of the workload
+	Name string
+
+	SegmentCounter map[component.NodeStatus][]PodWithMetric
+
+	podMetricsDisabled bool
+
+	mu *sync.Mutex
+}
+
+// NewWorkload creates a workload.
+func NewWorkload(name, iconName string) *Workload {
+	w := &Workload{
+		IconName:       iconName,
+		Name:           name,
+		SegmentCounter: make(map[component.NodeStatus][]PodWithMetric),
+		mu:             &sync.Mutex{},
+	}
+
+	return w
+}
+
+func (w *Workload) DonutChart() (*component.DonutChart, error) {
+	chart := component.NewDonutChart()
+
+	segments := w.donutSegments()
+	sort.Slice(segments, func(i, j int) bool {
+		return segments[i].Status < segments[j].Status
+	})
+
+	chart.SetSegments(segments)
+	chart.SetLabels("Pods", "Pod")
+
+	return chart, nil
+}
+
+// PodsWithMetrics returns a slice of PodWithMetric.
+func (w *Workload) PodsWithMetrics() []PodWithMetric {
+	var list []PodWithMetric
+	for _, podWithMetric := range w.SegmentCounter {
+		list = append(list, podWithMetric...)
+	}
+	return list
+}
+
+// AddStatus adds a pod status to the workload.
+func (w *Workload) AddPodStatus(status component.NodeStatus, object *unstructured.Unstructured, resourceList corev1.ResourceList) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	podWithMetrics, ok := w.SegmentCounter[status]
+	if !ok {
+		podWithMetrics = []PodWithMetric{}
+	}
+	pwm := PodWithMetric{
+		Pod:          object,
+		ResourceList: resourceList,
+	}
+	podWithMetrics = append(podWithMetrics, pwm)
+	w.SegmentCounter[status] = podWithMetrics
+}
+
+func (w *Workload) SetPodMetricsDisabled() {
+	w.podMetricsDisabled = true
+}
+
+func (w *Workload) PodMetricsEnabled() bool {
+	return !w.podMetricsDisabled
+}
+
+func (w *Workload) donutSegments() []component.DonutSegment {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	var segments []component.DonutSegment
+
+	for k, v := range w.SegmentCounter {
+		segments = append(segments, component.DonutSegment{
+			Count:  len(v),
+			Status: k,
+		})
+	}
+
+	return segments
+}
+
+func sortWorkloads(workloads []Workload) {
+	sort.SliceStable(workloads, func(i, j int) bool {
+		return workloads[i].Name < workloads[j].Name
+	})
+}
+
+func printResourceUsage(resourceType corev1.ResourceName, quantity resource.Quantity) string {
+	switch resourceType {
+	case corev1.ResourceCPU:
+		return fmt.Sprintf("%vm", quantity.MilliValue())
+	case corev1.ResourceMemory:
+		return fmt.Sprintf("%vMi", quantity.Value()/(1024*1024))
+	default:
+		return fmt.Sprintf("%v", quantity.Value())
+	}
+}
+
+func objectOwner(ctx context.Context, objectStore store.Store, object *unstructured.Unstructured) (*unstructured.Unstructured, error) {
+	if object == nil {
+		return nil, fmt.Errorf("can't find owner for nil object")
+	}
+
+	ownerReferences := object.GetOwnerReferences()
+	if len(ownerReferences) == 0 {
+		return object, nil
+	} else if len(ownerReferences) > 1 {
+		return object, nil
+	}
+
+	ownerReference := ownerReferences[0]
+	ownerRefKey := store.Key{
+		Namespace:  object.GetNamespace(),
+		APIVersion: ownerReference.APIVersion,
+		Kind:       ownerReference.Kind,
+		Name:       ownerReference.Name,
+	}
+
+	owner, found, err := objectStore.Get(ctx, ownerRefKey)
+	if err != nil {
+		return nil, fmt.Errorf("find object %s: %w", ownerRefKey, err)
+	}
+
+	if !found {
+		// TODO: does this need a better error?
+		return nil, fmt.Errorf("unable to find %s: %w", ownerRefKey, err)
+	}
+
+	return objectOwner(ctx, objectStore, owner)
+}
+
+func parseAsResourceList(object map[string]interface{}, field string) (resource.Quantity, error) {
+	s, found, err := unstructured.NestedString(object, field)
+	if err != nil {
+		return resource.Quantity{}, fmt.Errorf("parse %s: %w", field, err)
+	}
+
+	if !found {
+		return resource.Quantity{}, fmt.Errorf("unable to find field '%s'", field)
+	}
+
+	return resource.ParseQuantity(s)
+}
+
+func addResourceList(a corev1.ResourceList, q resource.Quantity, resourceName corev1.ResourceName) corev1.ResourceList {
+	rl := a.DeepCopy()
+
+	orig := a[resourceName]
+	orig.Add(q)
+	rl[resourceName] = orig
+
+	return rl
+}
+
+// WorkloadLoaderInterface loads workloads from a namespace.
+type WorkloadLoaderInterface interface {
+	// Load loads workloads from a namespace.
+	Load(ctx context.Context, namespace string) ([]Workload, error)
+}
+
+// WorkloadCardCollector creates cards for workloads in a namespace.
+type WorkloadCardCollector struct {
+	WorkloadLoader WorkloadLoaderInterface
+}
+
+// NewWorkloadCardCollector creates an instance of WorkloadCardCollector.
+func NewWorkloadCardCollector(loader WorkloadLoaderInterface) (*WorkloadCardCollector, error) {
+	if loader == nil {
+		return nil, fmt.Errorf("workload loader is nil")
+	}
+
+	return &WorkloadCardCollector{
+		WorkloadLoader: loader,
+	}, nil
+}
+
+// Collect collects cards.
+func (wc *WorkloadCardCollector) Collect(ctx context.Context, namespace string) ([]*component.Card, bool, error) {
+	workloads, err := wc.WorkloadLoader.Load(ctx, namespace)
+	if err != nil {
+		return nil, false, fmt.Errorf("load workloads: %w", err)
+	}
+
+	var cards []*component.Card
+
+	fullMetrics := true
+
+	for i := range workloads {
+		workload := &workloads[i]
+		summaryChart, err := workload.DonutChart()
+		if err != nil {
+			return nil, false, fmt.Errorf("create workload summary: %w", err)
+		}
+
+		section := component.FlexLayoutSection{
+			{
+				Width: component.WidthFull,
+				View:  summaryChart,
+			},
+		}
+
+		if workload.PodMetricsEnabled() {
+			memoryStat, err := PodMemoryStat(workload)
+			if err != nil {
+				return nil, false, fmt.Errorf("create workload memory chart: %w", err)
+			}
+
+			cpuStat, err := PodCPUStat(workload)
+			if err != nil {
+				return nil, false, fmt.Errorf("create workload cpu chart: %w", err)
+			}
+
+			section = component.FlexLayoutSection{
+				{
+					Width: component.WidthThird,
+					View:  summaryChart,
+				},
+				{
+					Width: component.WidthThird,
+					View:  memoryStat,
+				},
+				{
+					Width: component.WidthThird,
+					View:  cpuStat,
+				},
+			}
+			section = append(section, []component.FlexLayoutItem{}...)
+		} else {
+			fullMetrics = false
+		}
+
+		card := component.NewCard(component.TitleFromString(workload.Name))
+
+		layout := component.NewFlexLayout(workload.Name)
+
+		layout.AddSections(section)
+		card.SetBody(layout)
+
+		cards = append(cards, card)
+	}
+
+	return cards, fullMetrics, nil
+}
+
+// ClusterWorkloadLoaderOption is option for configuring ClusterWorkloadLoader.
+type ClusterWorkloadLoaderOption func(wl *ClusterWorkloadLoader)
+
+// ClusterWorkloadLoader loads workloads from a Kubernetes cluster.
+type ClusterWorkloadLoader struct {
+	ObjectStatuser   func(context.Context, runtime.Object, store.Store) (objectstatus.ObjectStatus, error)
+	ObjectStore      store.Store
+	PodMetricsLoader PodMetricsLoader
+}
+
+// NewWorkloadLoader creates an instance of ClusterWorkloadLoader.
+func NewClusterWorkloadLoader(objectStore store.Store, pml PodMetricsLoader, options ...ClusterWorkloadLoaderOption) (*ClusterWorkloadLoader, error) {
+	wl := &ClusterWorkloadLoader{
+		ObjectStatuser:   objectstatus.Status,
+		ObjectStore:      objectStore,
+		PodMetricsLoader: pml,
+	}
+
+	for _, option := range options {
+		option(wl)
+	}
+
+	if wl.ObjectStore == nil {
+		return nil, fmt.Errorf("object store is nil")
+	}
+
+	if wl.PodMetricsLoader == nil {
+		return nil, fmt.Errorf("pod metrics loader is nil")
+	}
+
+	return wl, nil
+}
+
+// Load loads workloads from a cluster.
+func (wl *ClusterWorkloadLoader) Load(ctx context.Context, namespace string) ([]Workload, error) {
+	if namespace == "" {
+		return nil, fmt.Errorf("namespace is blank")
+	}
+
+	podKey := store.Key{
+		Namespace:  namespace,
+		APIVersion: "v1",
+		Kind:       "Pod",
+	}
+
+	podList, _, err := wl.ObjectStore.List(ctx, podKey)
+	if err != nil {
+		return nil, fmt.Errorf("unable to list pods in '%s': %w", namespace, err)
+	}
+
+	workloadTracker := make(map[types.UID]*Workload)
+
+	for i := range podList.Items {
+		object := &podList.Items[i]
+
+		status, err := wl.ObjectStatuser(ctx, object, wl.ObjectStore)
+		if err != nil {
+			return nil, fmt.Errorf("get status for pod '%s': %w", object.GetName(), err)
+		}
+
+		owner, err := objectOwner(ctx, wl.ObjectStore, object)
+		if err != nil {
+			return nil, fmt.Errorf("find owner for pod '%s': %w", object.GetName(), err)
+		}
+
+		uid := owner.GetUID()
+
+		if _, ok := workloadTracker[uid]; !ok {
+			workloadTracker[uid] = NewWorkload(owner.GetName(), "application")
+		}
+
+		workload := workloadTracker[uid]
+
+		resourceList, err := wl.podMetrics(object)
+		if err != nil {
+			if IsPodMetricsNotSupported(err) {
+				workload.SetPodMetricsDisabled()
+				workload.AddPodStatus(status.Status(), object, corev1.ResourceList{})
+
+				continue
+			} else {
+				return nil, fmt.Errorf("get metrics for pod '%s': %w", object.GetName(), err)
+			}
+		}
+
+		workload.AddPodStatus(status.Status(), object, *resourceList)
+	}
+
+	var workloads []Workload
+	for k := range workloadTracker {
+		workloads = append(workloads, *workloadTracker[k])
+
+	}
+
+	sortWorkloads(workloads)
+	return workloads, nil
+}
+
+func (wl *ClusterWorkloadLoader) podMetrics(pod *unstructured.Unstructured) (*corev1.ResourceList, error) {
+	if pod == nil {
+		return nil, fmt.Errorf("pod is nil")
+	}
+
+	supportsPodMetrics, err := wl.PodMetricsLoader.SupportsMetrics()
+	if err != nil {
+		return nil, fmt.Errorf("check for pod metrics support: %w", err)
+	}
+
+	if !supportsPodMetrics {
+		return nil, &NoPodMetricsErr{}
+	}
+
+	namespace := pod.GetNamespace()
+	name := pod.GetName()
+
+	object, err := wl.PodMetricsLoader.Load(namespace, name)
+	if err != nil {
+		return nil, fmt.Errorf("get pod metrics: %w", err)
+	}
+
+	containersRaw, found, err := unstructured.NestedSlice(object.Object, "containers")
+	if err != nil {
+		return nil, fmt.Errorf("get containers in pod metrics")
+	}
+	if !found {
+		return nil, fmt.Errorf("unable to find containers in pod metrics")
+	}
+
+	actual := corev1.ResourceList{}
+
+	for i := range containersRaw {
+		container, ok := containersRaw[i].(map[string]interface{})
+		if !ok {
+			return nil, fmt.Errorf("expected container to be an object; it was '%T'", containersRaw[i])
+		}
+
+		usage, found, err := unstructured.NestedMap(container, "usage")
+		if err != nil {
+			return nil, fmt.Errorf("get container metric usage: %w", err)
+		}
+
+		if !found {
+			return nil, fmt.Errorf("container metric usage was not found")
+		}
+
+		cpu, err := parseAsResourceList(usage, "cpu")
+		if err != nil {
+			return nil, fmt.Errorf("parse memory from resource list: %w", err)
+		}
+		memory, err := parseAsResourceList(usage, "memory")
+		if err != nil {
+			return nil, fmt.Errorf("parse memory from resource list: %w", err)
+		}
+
+		actual = addResourceList(actual, cpu, corev1.ResourceCPU)
+		actual = addResourceList(actual, memory, corev1.ResourceMemory)
+	}
+
+	return &actual, nil
+}
+
+// PodCPUStat creates a single stat component for pod cpu. It will summarize all the pods in the workload.
+func PodCPUStat(workload *Workload) (*component.SingleStat, error) {
+	return podStat(workload, corev1.ResourceCPU, "CPU(cores)")
+}
+
+// PodMemoryStat creates a single stats component for pod memory. It will summarize all the pods in the workload.
+func PodMemoryStat(workload *Workload) (*component.SingleStat, error) {
+	return podStat(workload, corev1.ResourceMemory, "Memory(bytes)")
+}
+
+func podStat(workload *Workload, resourceName corev1.ResourceName, title string) (*component.SingleStat, error) {
+	resources, metrics, err := summarizePodWithMetric(workload)
+	if err != nil {
+		return nil, err
+	}
+
+	background := WorkloadStatusColorOK
+
+	actual := metrics[resourceName]
+
+	av := actual.Value()
+	r := resources.Requests[resourceName]
+	rv := r.Value()
+	l := resources.Limits[resourceName]
+	lv := l.Value()
+	if resourceName == corev1.ResourceCPU {
+		av = actual.MilliValue()
+		rv = r.MilliValue()
+		lv = l.MilliValue()
+	}
+
+	if !r.IsZero() && av > rv {
+		background = WorkloadStatusColorWarning
+	}
+
+	if !l.IsZero() && av > lv {
+		background = WorkloadStatusColorError
+	}
+
+	m := metrics[resourceName]
+	stat := component.NewSingleStat(title, printResourceUsage(resourceName, m), background)
+	return stat, nil
+}
+
+func summarizePodWithMetric(workload *Workload) (corev1.ResourceRequirements, corev1.ResourceList, error) {
+	if workload == nil {
+		return corev1.ResourceRequirements{}, corev1.ResourceList{}, fmt.Errorf("workload is nil")
+	}
+
+	resources := corev1.ResourceRequirements{}
+	metrics := corev1.ResourceList{}
+
+	for _, podWithMetric := range workload.PodsWithMetrics() {
+		pr, err := summarizePodResources(podWithMetric.Pod)
+		if err != nil {
+			return corev1.ResourceRequirements{}, corev1.ResourceList{}, fmt.Errorf("summarize pod resources: %w", err)
+		}
+
+		resources = CombineResourceRequirements(resources, *pr)
+		metrics = addResourceList(metrics, *podWithMetric.ResourceList.Memory(), corev1.ResourceMemory)
+		metrics = addResourceList(metrics, *podWithMetric.ResourceList.Cpu(), corev1.ResourceCPU)
+	}
+
+	return resources, metrics, nil
+}
+
+// CombineResourceRequirements combines two resource requirements into a new resource requirement.
+func CombineResourceRequirements(a, b corev1.ResourceRequirements) corev1.ResourceRequirements {
+	out := corev1.ResourceRequirements{
+		Limits:   corev1.ResourceList{},
+		Requests: corev1.ResourceList{},
+	}
+
+	out.Limits = addResourceList(out.Limits, *a.Limits.Cpu(), corev1.ResourceCPU)
+	out.Limits = addResourceList(out.Limits, *a.Limits.Memory(), corev1.ResourceMemory)
+	out.Limits = addResourceList(out.Limits, *b.Limits.Cpu(), corev1.ResourceCPU)
+	out.Limits = addResourceList(out.Limits, *b.Limits.Memory(), corev1.ResourceMemory)
+	out.Requests = addResourceList(out.Requests, *a.Requests.Cpu(), corev1.ResourceCPU)
+	out.Requests = addResourceList(out.Requests, *a.Requests.Memory(), corev1.ResourceMemory)
+	out.Requests = addResourceList(out.Requests, *b.Requests.Cpu(), corev1.ResourceCPU)
+	out.Requests = addResourceList(out.Requests, *b.Requests.Memory(), corev1.ResourceMemory)
+
+	return out
+}
+
+func summarizePodResources(object *unstructured.Unstructured) (*corev1.ResourceRequirements, error) {
+	if object == nil {
+		return nil, fmt.Errorf("object is nil")
+	}
+
+	var pod corev1.Pod
+	if err := scheme.Scheme.Convert(object, &pod, nil); err != nil {
+		return nil, fmt.Errorf("object is not a pod: %w", err)
+	}
+
+	requirements := corev1.ResourceRequirements{
+		Limits:   corev1.ResourceList{},
+		Requests: corev1.ResourceList{},
+	}
+
+	containers := pod.Spec.Containers
+	for i := range containers {
+		limit := containers[i].Resources.Limits
+		request := containers[i].Resources.Requests
+
+		requirements.Limits = addResourceList(requirements.Limits, *limit.Cpu(), corev1.ResourceCPU)
+		requirements.Limits = addResourceList(requirements.Limits, *limit.Memory(), corev1.ResourceMemory)
+		requirements.Requests = addResourceList(requirements.Requests, *request.Cpu(), corev1.ResourceCPU)
+		requirements.Requests = addResourceList(requirements.Requests, *request.Memory(), corev1.ResourceMemory)
+	}
+
+	return &requirements, nil
+}
diff --git a/pkg/dash/dash.go b/pkg/dash/dash.go
index dc73e73b..e847761a 100644
--- a/pkg/dash/dash.go
+++ b/pkg/dash/dash.go
@@ -36,6 +36,7 @@ import (
 	"github.com/vmware-tanzu/octant/internal/modules/configuration"
 	"github.com/vmware-tanzu/octant/internal/modules/localcontent"
 	"github.com/vmware-tanzu/octant/internal/modules/overview"
+	"github.com/vmware-tanzu/octant/internal/modules/workloads"
 	"github.com/vmware-tanzu/octant/internal/objectstore"
 	"github.com/vmware-tanzu/octant/internal/portforward"
 	"github.com/vmware-tanzu/octant/pkg/action"
@@ -240,6 +241,16 @@ type moduleOptions struct {
 func initModules(ctx context.Context, dashConfig config.Dash, namespace string, options Options) ([]module.Module, error) {
 	var list []module.Module
 
+	podViewOptions := workloads.Options{
+		DashConfig: dashConfig,
+	}
+	workloadModule, err := workloads.New(ctx, podViewOptions)
+	if err != nil {
+		return nil, fmt.Errorf("initialize workload module: %w", err)
+	}
+
+	list = append(list, workloadModule)
+
 	if viper.GetBool("enable-feature-applications") {
 		applicationsOptions := applications.Options{
 			DashConfig: dashConfig,
diff --git a/pkg/view/component/base.go b/pkg/view/component/base.go
index c2019a48..c47b011c 100644
--- a/pkg/view/component/base.go
+++ b/pkg/view/component/base.go
@@ -6,34 +6,37 @@ SPDX-License-Identifier: Apache-2.0
 package component
 
 const (
-	typeAnnotations        = "annotations"
-	typeButtonGroup        = "buttonGroup"
-	typeCard               = "card"
-	typeCardList           = "cardList"
-	typeContainers         = "containers"
-	typeError              = "error"
-	typeExpressionSelector = "expressionSelector"
-	typeFlexLayout         = "flexlayout"
-	typeGraphviz           = "graphviz"
-	typeIFrame             = "iframe"
-	typeLabels             = "labels"
-	typeLabelSelector      = "labelSelector"
-	typeLink               = "link"
-	typeList               = "list"
-	typeLoading            = "loading"
-	typeLogs               = "logs"
-	typePodStatus          = "podStatus"
-	typePort               = "port"
-	typePorts              = "ports"
-	typePortForward        = "portforward"
-	typeQuadrant           = "quadrant"
-	typeResourceViewer     = "resourceViewer"
-	typeSelectors          = "selectors"
-	typeSummary            = "summary"
-	typeTable              = "table"
-	typeText               = "text"
-	typeTimestamp          = "timestamp"
-	typeYAML               = "yaml"
+	typeAnnotations         = "annotations"
+	typeButtonGroup         = "buttonGroup"
+	typeCard                = "card"
+	typeCardList            = "cardList"
+	typeContainers          = "containers"
+	typeDonutChart          = "donutChart"
+	typeError               = "error"
+	typeExpressionSelector  = "expressionSelector"
+	typeFlexLayout          = "flexlayout"
+	typeGraphviz            = "graphviz"
+	typeIFrame              = "iframe"
+	typeLabels              = "labels"
+	typeLabelSelector       = "labelSelector"
+	typeLink                = "link"
+	typeList                = "list"
+	typeLoading             = "loading"
+	typeLogs                = "logs"
+	typePodStatus           = "podStatus"
+	typePort                = "port"
+	typePorts               = "ports"
+	typePortForward         = "portforward"
+	typeQuadrant            = "quadrant"
+	typeResourceViewer      = "resourceViewer"
+	typeSelectors           = "selectors"
+	typeSingleStat          = "singleStat"
+	typeSummary             = "summary"
+	typeTable               = "table"
+	typeText                = "text"
+	typeTimestamp           = "timestamp"
+	typeVerticalBulletChart = "verticalBulletChart"
+	typeYAML                = "yaml"
 )
 
 // base is a base component.
diff --git a/pkg/view/component/card.go b/pkg/view/component/card.go
index 21c12a2d..84428f1a 100644
--- a/pkg/view/component/card.go
+++ b/pkg/view/component/card.go
@@ -48,9 +48,9 @@ type Card struct {
 }
 
 // NewCard creates a card component.
-func NewCard(title string) *Card {
+func NewCard(title []TitleComponent) *Card {
 	return &Card{
-		base: newBase(typeCard, TitleFromString(title)),
+		base: newBase(typeCard, title),
 	}
 }
 
diff --git a/pkg/view/component/donut_chart.go b/pkg/view/component/donut_chart.go
new file mode 100644
index 00000000..3dc9a4cd
--- /dev/null
+++ b/pkg/view/component/donut_chart.go
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package component
+
+import "encoding/json"
+
+type DonutChartLabels struct {
+	Plural   string `json:"plural"`
+	Singular string `json:"singular"`
+}
+
+type DonutSegment struct {
+	Count  int        `json:"count"`
+	Status NodeStatus `json:"status"`
+}
+
+type DonutChartConfig struct {
+	Segments []DonutSegment   `json:"segments"`
+	Labels   DonutChartLabels `json:"labels"`
+}
+
+type DonutChart struct {
+	base
+	Config DonutChartConfig `json:"config"`
+}
+
+var _ Component = (*DonutChart)(nil)
+
+func NewDonutChart() *DonutChart {
+	dc := &DonutChart{
+		base: newBase(typeDonutChart, nil),
+	}
+
+	return dc
+}
+
+type donutChartMarshal DonutChart
+
+func (dc *DonutChart) SetSegments(segments []DonutSegment) {
+	dc.Config.Segments = segments
+}
+
+func (dc *DonutChart) SetLabels(plural string, singular string) {
+	dc.Config.Labels = DonutChartLabels{
+		Plural:   plural,
+		Singular: singular,
+	}
+}
+
+func (dc *DonutChart) MarshalJSON() ([]byte, error) {
+	m := donutChartMarshal(*dc)
+	m.Metadata.Type = typeDonutChart
+	return json.Marshal(&m)
+}
diff --git a/pkg/view/component/flexlayout.go b/pkg/view/component/flexlayout.go
index 4b733eeb..26ed0e31 100644
--- a/pkg/view/component/flexlayout.go
+++ b/pkg/view/component/flexlayout.go
@@ -8,12 +8,14 @@ package component
 import "encoding/json"
 
 const (
+	// WidthFull is a full width section.
+	WidthFull int = 24
 	// WidthQuarter is a quarter width section.
 	WidthQuarter int = 6
 	// WidthHalf is a half width section.
 	WidthHalf int = 12
-	// WidthFull is a full width section.
-	WidthFull int = 24
+	// WidthThird is a third width section.
+	WidthThird int = 8
 )
 
 // FlexLayoutItem is an item in a flex layout.
@@ -67,10 +69,7 @@ func NewFlexLayout(title string) *FlexLayout {
 	}
 }
 
-// GetMetadata returns the metadata for the flex layout view.
-func (fl *FlexLayout) GetMetadata() Metadata {
-	return fl.Metadata
-}
+var _ Component = (*FlexLayout)(nil)
 
 // AddSections adds one or more sections to the flex layout.
 func (fl *FlexLayout) AddSections(sections ...FlexLayoutSection) {
diff --git a/pkg/view/component/single_stat.go b/pkg/view/component/single_stat.go
new file mode 100644
index 00000000..5b51e23a
--- /dev/null
+++ b/pkg/view/component/single_stat.go
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package component
+
+import "encoding/json"
+
+type SingleStatValue struct {
+	Text  string `json:"text"`
+	Color string `json:"color"`
+}
+
+type SingleStateConfig struct {
+	Title string          `json:"title"`
+	Value SingleStatValue `json:"value"`
+}
+
+type SingleStat struct {
+	base
+	Config SingleStateConfig `json:"config"`
+}
+
+var _ Component = (*SingleStat)(nil)
+
+func NewSingleStat(title, valueText, color string) *SingleStat {
+	return &SingleStat{
+		base: newBase(typeSingleStat, nil),
+		Config: SingleStateConfig{
+			Title: title,
+			Value: SingleStatValue{
+				Text:  valueText,
+				Color: color,
+			},
+		},
+	}
+}
+
+type singleStatMarshal SingleStat
+
+func (ss *SingleStat) MarshalJSON() ([]byte, error) {
+	m := singleStatMarshal(*ss)
+	m.Metadata.Type = typeSingleStat
+	return json.Marshal(&m)
+}
diff --git a/pkg/view/component/veritical_bullet.go b/pkg/view/component/veritical_bullet.go
new file mode 100644
index 00000000..31ab7108
--- /dev/null
+++ b/pkg/view/component/veritical_bullet.go
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package component
+
+import "encoding/json"
+
+type ChartColor string
+
+const (
+	ChartColorError   ChartColor = "#f52f22"
+	ChartColorWarning ChartColor = "#fac400"
+	ChartColorOK      ChartColor = "#60b515"
+)
+
+type BulletBand struct {
+	Min   int        `json:"min"`
+	Max   int        `json:"max"`
+	Color ChartColor `json:"color"`
+	Label string     `json:"label"`
+}
+
+type VerticalBulletChartConfig struct {
+	Bands        []BulletBand `json:"bands"`
+	Measure      int          `json:"measure"`
+	MeasureLabel string       `json:"measureLabel"`
+	Label        string       `json:"label"`
+}
+
+type VerticalBulletChart struct {
+	base
+	Config VerticalBulletChartConfig `json:"config"`
+}
+
+var _ Component = (*VerticalBulletChart)(nil)
+
+func NewVerticalBulletChart(label string) *VerticalBulletChart {
+	return &VerticalBulletChart{
+		base: newBase(typeVerticalBulletChart, nil),
+		Config: VerticalBulletChartConfig{
+			Label: label,
+		},
+	}
+}
+
+func (vbc *VerticalBulletChart) SetBands(bands []BulletBand) {
+	vbc.Config.Bands = bands
+}
+
+func (vbc *VerticalBulletChart) SetMeasure(label string, val int) {
+	vbc.Config.MeasureLabel = label
+	vbc.Config.Measure = val
+}
+
+type verticalBulletChartMarshal VerticalBulletChart
+
+func (vbc *VerticalBulletChart) MarshalJSON() ([]byte, error) {
+	m := verticalBulletChartMarshal(*vbc)
+	m.Metadata.Type = typeVerticalBulletChart
+	return json.Marshal(&m)
+}
diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go
index 72c0c5d6..172effdd 100644
--- a/vendor/golang.org/x/tools/go/packages/golist.go
+++ b/vendor/golang.org/x/tools/go/packages/golist.go
@@ -158,7 +158,7 @@ extractQueries:
 		}
 	}
 
-	modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response.dr)
+	modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response)
 	if err != nil {
 		return nil, err
 	}
@@ -209,7 +209,7 @@ func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDedu
 	for _, pkg := range dr.Packages {
 		response.addPackage(pkg)
 	}
-	_, needPkgs, err := processGolistOverlay(cfg, response.dr)
+	_, needPkgs, err := processGolistOverlay(cfg, response)
 	if err != nil {
 		return err
 	}
@@ -229,7 +229,13 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
 		}
 		dirResponse, err := driver(cfg, pattern)
 		if err != nil {
-			return err
+			// Couldn't find a package for the directory. Try to load the file as an ad-hoc package.
+			var queryErr error
+			dirResponse, err = driver(cfg, query)
+			if queryErr != nil {
+				// Return the original error if the attempt to fall back failed.
+				return err
+			}
 		}
 		isRoot := make(map[string]bool, len(dirResponse.Roots))
 		for _, root := range dirResponse.Roots {
@@ -693,7 +699,7 @@ func golistDriver(cfg *Config, words ...string) (*driverResponse, error) {
 		if p.Error != nil {
 			pkg.Errors = append(pkg.Errors, Error{
 				Pos: p.Error.Pos,
-				Msg: p.Error.Err,
+				Msg: strings.TrimSpace(p.Error.Err), // Trim to work around golang.org/issue/32363.
 			})
 		}
 
diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go
index ce322ce5..b35399ef 100644
--- a/vendor/golang.org/x/tools/go/packages/golist_overlay.go
+++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go
@@ -3,6 +3,7 @@ package packages
 import (
 	"bytes"
 	"encoding/json"
+	"fmt"
 	"go/parser"
 	"go/token"
 	"path"
@@ -16,16 +17,13 @@ import (
 // files that don't exist on disk to an overlay. The results can be
 // sometimes incorrect.
 // TODO(matloob): Handle unsupported cases, including the following:
-// - test files
-// - adding test and non-test files to test variants of packages
 // - determining the correct package to add given a new import path
-// - creating packages that don't exist
-func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs, needPkgs []string, err error) {
+func processGolistOverlay(cfg *Config, response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) {
 	havePkgs := make(map[string]string) // importPath -> non-test package ID
 	needPkgsSet := make(map[string]bool)
 	modifiedPkgsSet := make(map[string]bool)
 
-	for _, pkg := range response.Packages {
+	for _, pkg := range response.dr.Packages {
 		// This is an approximation of import path to id. This can be
 		// wrong for tests, vendored packages, and a number of other cases.
 		havePkgs[pkg.PkgPath] = pkg.ID
@@ -36,19 +34,33 @@ func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs,
 
 	for opath, contents := range cfg.Overlay {
 		base := filepath.Base(opath)
-		if strings.HasSuffix(opath, "_test.go") {
-			// Overlays don't support adding new test files yet.
-			// TODO(matloob): support adding new test files.
-			continue
-		}
 		dir := filepath.Dir(opath)
 		var pkg *Package
+		var testVariantOf *Package // if opath is a test file, this is the package it is testing
 		var fileExists bool
-		for _, p := range response.Packages {
+		isTest := strings.HasSuffix(opath, "_test.go")
+		pkgName, ok := extractPackageName(opath, contents)
+		if !ok {
+			// Don't bother adding a file that doesn't even have a parsable package statement
+			// to the overlay.
+			continue
+		}
+	nextPackage:
+		for _, p := range response.dr.Packages {
+			if pkgName != p.Name {
+				continue
+			}
 			for _, f := range p.GoFiles {
 				if !sameFile(filepath.Dir(f), dir) {
 					continue
 				}
+				if isTest && !hasTestFiles(p) {
+					// TODO(matloob): Are there packages other than the 'production' variant
+					// of a package that this can match? This shouldn't match the test main package
+					// because the file is generated in another directory.
+					testVariantOf = p
+					continue nextPackage
+				}
 				pkg = p
 				if filepath.Base(f) == base {
 					fileExists = true
@@ -82,13 +94,16 @@ func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs,
 			if pkgPath == "" {
 				continue
 			}
-			pkgName, ok := extractPackageName(opath, contents)
-			if !ok {
-				continue
+			isXTest := strings.HasSuffix(pkgName, "_test")
+			if isXTest {
+				pkgPath += "_test"
 			}
 			id := pkgPath
+			if isTest && !isXTest {
+				id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath)
+			}
 			// Try to reclaim a package with the same id if it exists in the response.
-			for _, p := range response.Packages {
+			for _, p := range response.dr.Packages {
 				if reclaimPackage(p, id, opath, contents) {
 					pkg = p
 					break
@@ -97,9 +112,13 @@ func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs,
 			// Otherwise, create a new package
 			if pkg == nil {
 				pkg = &Package{PkgPath: pkgPath, ID: id, Name: pkgName, Imports: make(map[string]*Package)}
-				// TODO(matloob): Is it okay to amend response.Packages this way?
-				response.Packages = append(response.Packages, pkg)
+				response.addPackage(pkg)
 				havePkgs[pkg.PkgPath] = id
+				// Add the production package's sources for a test variant.
+				if isTest && !isXTest && testVariantOf != nil {
+					pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
+					pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
+				}
 			}
 		}
 		if !fileExists {
@@ -143,7 +162,7 @@ func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs,
 
 	// Do another pass now that new packages have been created to determine the
 	// set of missing packages.
-	for _, pkg := range response.Packages {
+	for _, pkg := range response.dr.Packages {
 		for _, imp := range pkg.Imports {
 			pkgPath := toPkgPath(imp.ID)
 			if _, ok := havePkgs[pkgPath]; !ok {
@@ -163,6 +182,15 @@ func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs,
 	return modifiedPkgs, needPkgs, err
 }
 
+func hasTestFiles(p *Package) bool {
+	for _, f := range p.GoFiles {
+		if strings.HasSuffix(f, "_test.go") {
+			return true
+		}
+	}
+	return false
+}
+
 // determineRootDirs returns a mapping from directories code can be contained in to the
 // corresponding import path prefixes of those directories.
 // Its result is used to try to determine the import path for a package containing
diff --git a/vendor/k8s.io/metrics/LICENSE b/vendor/k8s.io/metrics/LICENSE
new file mode 100644
index 00000000..8dada3ed
--- /dev/null
+++ b/vendor/k8s.io/metrics/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/doc.go b/vendor/k8s.io/metrics/pkg/apis/metrics/doc.go
new file mode 100644
index 00000000..49e29972
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/doc.go
@@ -0,0 +1,21 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+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.
+*/
+
+// +k8s:deepcopy-gen=package
+// +groupName=metrics.k8s.io
+
+// Package metrics defines an API for exposing metics.
+package metrics // import "k8s.io/metrics/pkg/apis/metrics"
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/register.go b/vendor/k8s.io/metrics/pkg/apis/metrics/register.go
new file mode 100644
index 00000000..9729b08f
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/register.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+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.
+*/
+
+package metrics
+
+import (
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// GroupName is the group name use in this package
+const GroupName = "metrics.k8s.io"
+
+// SchemeGroupVersion is group version used to register these objects
+var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
+
+// Kind takes an unqualified kind and returns back a Group qualified GroupKind
+func Kind(kind string) schema.GroupKind {
+	return SchemeGroupVersion.WithKind(kind).GroupKind()
+}
+
+// Resource takes an unqualified resource and returns back a Group qualified GroupResource
+func Resource(resource string) schema.GroupResource {
+	return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
+
+var (
+	SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
+	AddToScheme   = SchemeBuilder.AddToScheme
+)
+
+func addKnownTypes(scheme *runtime.Scheme) error {
+	scheme.AddKnownTypes(SchemeGroupVersion,
+		&NodeMetrics{},
+		&NodeMetricsList{},
+		&PodMetrics{},
+		&PodMetricsList{},
+	)
+	return nil
+}
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/types.go b/vendor/k8s.io/metrics/pkg/apis/metrics/types.go
new file mode 100644
index 00000000..5d90b318
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/types.go
@@ -0,0 +1,95 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+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.
+*/
+
+package metrics
+
+import (
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// +genclient
+// +resourceName=nodes
+// +genclient:readonly
+// +genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// resource usage metrics of a node.
+type NodeMetrics struct {
+	metav1.TypeMeta
+	metav1.ObjectMeta
+
+	// The following fields define time interval from which metrics were
+	// collected from the interval [Timestamp-Window, Timestamp].
+	Timestamp metav1.Time
+	Window    metav1.Duration
+
+	// The memory usage is the memory working set.
+	Usage corev1.ResourceList
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// NodeMetricsList is a list of NodeMetrics.
+type NodeMetricsList struct {
+	metav1.TypeMeta
+	// Standard list metadata.
+	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+	metav1.ListMeta
+
+	// List of node metrics.
+	Items []NodeMetrics
+}
+
+// +genclient
+// +resourceName=pods
+// +genclient:readonly
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// resource usage metrics of a pod.
+type PodMetrics struct {
+	metav1.TypeMeta
+	metav1.ObjectMeta
+
+	// The following fields define time interval from which metrics were
+	// collected from the interval [Timestamp-Window, Timestamp].
+	Timestamp metav1.Time
+	Window    metav1.Duration
+
+	// Metrics for all containers are collected within the same time window.
+	Containers []ContainerMetrics
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// PodMetricsList is a list of PodMetrics.
+type PodMetricsList struct {
+	metav1.TypeMeta
+	// Standard list metadata.
+	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+	metav1.ListMeta
+
+	// List of pod metrics.
+	Items []PodMetrics
+}
+
+// resource usage metrics of a container.
+type ContainerMetrics struct {
+	// Container name corresponding to the one from pod.spec.containers.
+	Name string
+	// The memory usage is the memory working set.
+	Usage corev1.ResourceList
+}
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/doc.go b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/doc.go
new file mode 100644
index 00000000..deb88283
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/doc.go
@@ -0,0 +1,24 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+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.
+*/
+
+// +k8s:deepcopy-gen=package
+// +k8s:protobuf-gen=package
+// +k8s:conversion-gen=k8s.io/metrics/pkg/apis/metrics
+// +k8s:openapi-gen=true
+// +groupName=metrics.k8s.io
+
+// Package v1beta1 is the v1beta1 version of the metrics API.
+package metricsv1beta1 // import "k8s.io/metrics/pkg/apis/metrics/v1beta1"
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.pb.go b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.pb.go
new file mode 100644
index 00000000..b4aee6a6
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.pb.go
@@ -0,0 +1,1799 @@
+/*
+Copyright The Kubernetes Authors.
+
+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.
+*/
+
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: k8s.io/kubernetes/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.proto
+
+package metricsv1beta1
+
+import (
+	fmt "fmt"
+
+	io "io"
+
+	proto "github.com/gogo/protobuf/proto"
+	github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
+
+	k8s_io_api_core_v1 "k8s.io/api/core/v1"
+	k8s_io_apimachinery_pkg_api_resource "k8s.io/apimachinery/pkg/api/resource"
+	resource "k8s.io/apimachinery/pkg/api/resource"
+
+	math "math"
+	math_bits "math/bits"
+	reflect "reflect"
+	strings "strings"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+func (m *ContainerMetrics) Reset()      { *m = ContainerMetrics{} }
+func (*ContainerMetrics) ProtoMessage() {}
+func (*ContainerMetrics) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3e7a045767f4b09f, []int{0}
+}
+func (m *ContainerMetrics) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ContainerMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	b = b[:cap(b)]
+	n, err := m.MarshalToSizedBuffer(b)
+	if err != nil {
+		return nil, err
+	}
+	return b[:n], nil
+}
+func (m *ContainerMetrics) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ContainerMetrics.Merge(m, src)
+}
+func (m *ContainerMetrics) XXX_Size() int {
+	return m.Size()
+}
+func (m *ContainerMetrics) XXX_DiscardUnknown() {
+	xxx_messageInfo_ContainerMetrics.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ContainerMetrics proto.InternalMessageInfo
+
+func (m *NodeMetrics) Reset()      { *m = NodeMetrics{} }
+func (*NodeMetrics) ProtoMessage() {}
+func (*NodeMetrics) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3e7a045767f4b09f, []int{1}
+}
+func (m *NodeMetrics) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *NodeMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	b = b[:cap(b)]
+	n, err := m.MarshalToSizedBuffer(b)
+	if err != nil {
+		return nil, err
+	}
+	return b[:n], nil
+}
+func (m *NodeMetrics) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NodeMetrics.Merge(m, src)
+}
+func (m *NodeMetrics) XXX_Size() int {
+	return m.Size()
+}
+func (m *NodeMetrics) XXX_DiscardUnknown() {
+	xxx_messageInfo_NodeMetrics.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_NodeMetrics proto.InternalMessageInfo
+
+func (m *NodeMetricsList) Reset()      { *m = NodeMetricsList{} }
+func (*NodeMetricsList) ProtoMessage() {}
+func (*NodeMetricsList) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3e7a045767f4b09f, []int{2}
+}
+func (m *NodeMetricsList) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *NodeMetricsList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	b = b[:cap(b)]
+	n, err := m.MarshalToSizedBuffer(b)
+	if err != nil {
+		return nil, err
+	}
+	return b[:n], nil
+}
+func (m *NodeMetricsList) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NodeMetricsList.Merge(m, src)
+}
+func (m *NodeMetricsList) XXX_Size() int {
+	return m.Size()
+}
+func (m *NodeMetricsList) XXX_DiscardUnknown() {
+	xxx_messageInfo_NodeMetricsList.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_NodeMetricsList proto.InternalMessageInfo
+
+func (m *PodMetrics) Reset()      { *m = PodMetrics{} }
+func (*PodMetrics) ProtoMessage() {}
+func (*PodMetrics) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3e7a045767f4b09f, []int{3}
+}
+func (m *PodMetrics) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *PodMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	b = b[:cap(b)]
+	n, err := m.MarshalToSizedBuffer(b)
+	if err != nil {
+		return nil, err
+	}
+	return b[:n], nil
+}
+func (m *PodMetrics) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PodMetrics.Merge(m, src)
+}
+func (m *PodMetrics) XXX_Size() int {
+	return m.Size()
+}
+func (m *PodMetrics) XXX_DiscardUnknown() {
+	xxx_messageInfo_PodMetrics.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PodMetrics proto.InternalMessageInfo
+
+func (m *PodMetricsList) Reset()      { *m = PodMetricsList{} }
+func (*PodMetricsList) ProtoMessage() {}
+func (*PodMetricsList) Descriptor() ([]byte, []int) {
+	return fileDescriptor_3e7a045767f4b09f, []int{4}
+}
+func (m *PodMetricsList) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *PodMetricsList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	b = b[:cap(b)]
+	n, err := m.MarshalToSizedBuffer(b)
+	if err != nil {
+		return nil, err
+	}
+	return b[:n], nil
+}
+func (m *PodMetricsList) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PodMetricsList.Merge(m, src)
+}
+func (m *PodMetricsList) XXX_Size() int {
+	return m.Size()
+}
+func (m *PodMetricsList) XXX_DiscardUnknown() {
+	xxx_messageInfo_PodMetricsList.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PodMetricsList proto.InternalMessageInfo
+
+func init() {
+	proto.RegisterType((*ContainerMetrics)(nil), "k8s.io.metrics.pkg.apis.metrics.v1beta1.ContainerMetrics")
+	proto.RegisterMapType((k8s_io_api_core_v1.ResourceList)(nil), "k8s.io.metrics.pkg.apis.metrics.v1beta1.ContainerMetrics.UsageEntry")
+	proto.RegisterType((*NodeMetrics)(nil), "k8s.io.metrics.pkg.apis.metrics.v1beta1.NodeMetrics")
+	proto.RegisterMapType((k8s_io_api_core_v1.ResourceList)(nil), "k8s.io.metrics.pkg.apis.metrics.v1beta1.NodeMetrics.UsageEntry")
+	proto.RegisterType((*NodeMetricsList)(nil), "k8s.io.metrics.pkg.apis.metrics.v1beta1.NodeMetricsList")
+	proto.RegisterType((*PodMetrics)(nil), "k8s.io.metrics.pkg.apis.metrics.v1beta1.PodMetrics")
+	proto.RegisterType((*PodMetricsList)(nil), "k8s.io.metrics.pkg.apis.metrics.v1beta1.PodMetricsList")
+}
+
+func init() {
+	proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.proto", fileDescriptor_3e7a045767f4b09f)
+}
+
+var fileDescriptor_3e7a045767f4b09f = []byte{
+	// 659 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x54, 0xbf, 0x6f, 0x13, 0x3f,
+	0x1c, 0x8d, 0x9b, 0xa4, 0xdf, 0xd6, 0xf9, 0x52, 0xca, 0x4d, 0x55, 0x86, 0x4b, 0x95, 0x85, 0x0a,
+	0xa9, 0x36, 0x2d, 0x15, 0x2a, 0x2c, 0x48, 0x47, 0x19, 0x90, 0x68, 0x29, 0xa7, 0xf2, 0x9b, 0x01,
+	0xe7, 0x62, 0x2e, 0x26, 0xdc, 0x39, 0xb2, 0x7d, 0xa9, 0xb2, 0xa1, 0x8a, 0x89, 0x09, 0xf1, 0x57,
+	0x45, 0x4c, 0x1d, 0x3b, 0xa0, 0x96, 0x84, 0x99, 0x7f, 0x80, 0x09, 0x9d, 0xcf, 0x97, 0x0b, 0x4d,
+	0x69, 0x8f, 0x0e, 0x4c, 0xdd, 0xee, 0x3e, 0xf6, 0x7b, 0xef, 0xe3, 0xf7, 0x79, 0x36, 0xdc, 0x6e,
+	0xaf, 0x4b, 0xc4, 0x38, 0x6e, 0x47, 0x0d, 0x2a, 0x42, 0xaa, 0xa8, 0xc4, 0x5d, 0x1a, 0x36, 0xb9,
+	0xc0, 0x66, 0x21, 0xa0, 0x4a, 0x30, 0x4f, 0xe2, 0x4e, 0xdb, 0xc7, 0xa4, 0xc3, 0xe4, 0xa8, 0xd0,
+	0x5d, 0x69, 0x50, 0x45, 0x56, 0xb0, 0x4f, 0x43, 0x2a, 0x88, 0xa2, 0x4d, 0xd4, 0x11, 0x5c, 0x71,
+	0xeb, 0x6a, 0x02, 0x44, 0x66, 0x1f, 0xea, 0xb4, 0x7d, 0x14, 0x03, 0x47, 0x05, 0x03, 0xac, 0x2e,
+	0xfb, 0x4c, 0xb5, 0xa2, 0x06, 0xf2, 0x78, 0x80, 0x7d, 0xee, 0x73, 0xac, 0xf1, 0x8d, 0xe8, 0x8d,
+	0xfe, 0xd3, 0x3f, 0xfa, 0x2b, 0xe1, 0xad, 0xd6, 0x4d, 0x43, 0xa4, 0xc3, 0xb0, 0xc7, 0x05, 0xc5,
+	0xdd, 0x09, 0xed, 0xea, 0x5a, 0xb6, 0x27, 0x20, 0x5e, 0x8b, 0x85, 0x54, 0xf4, 0xd2, 0xce, 0xb1,
+	0xa0, 0x92, 0x47, 0xc2, 0xa3, 0x7f, 0x85, 0xd2, 0xe7, 0x25, 0x27, 0x69, 0xe1, 0x3f, 0xa1, 0x44,
+	0x14, 0x2a, 0x16, 0x4c, 0xca, 0xdc, 0x3c, 0x0b, 0x20, 0xbd, 0x16, 0x0d, 0xc8, 0x71, 0x5c, 0x7d,
+	0xaf, 0x08, 0xe7, 0xef, 0xf2, 0x50, 0x91, 0x18, 0xb1, 0x99, 0x98, 0x68, 0x2d, 0xc2, 0x52, 0x48,
+	0x02, 0xba, 0x00, 0x16, 0xc1, 0xd2, 0xac, 0xf3, 0x7f, 0xff, 0xb0, 0x56, 0x18, 0x1e, 0xd6, 0x4a,
+	0x5b, 0x24, 0xa0, 0xae, 0x5e, 0xb1, 0x06, 0x00, 0x96, 0x23, 0x49, 0x7c, 0xba, 0x30, 0xb5, 0x58,
+	0x5c, 0xaa, 0xac, 0x6e, 0xa0, 0x9c, 0x83, 0x41, 0xc7, 0xc5, 0xd0, 0xe3, 0x98, 0xe6, 0x5e, 0xa8,
+	0x44, 0xcf, 0xf9, 0x00, 0x8c, 0x54, 0x59, 0x17, 0x7f, 0x1e, 0xd6, 0x6a, 0x93, 0x73, 0x41, 0xae,
+	0xb1, 0xfa, 0x01, 0x93, 0x6a, 0xef, 0xe8, 0xd4, 0x2d, 0x71, 0xc7, 0x1f, 0x8f, 0x6a, 0xcb, 0x79,
+	0x26, 0x87, 0x1e, 0x45, 0x24, 0x54, 0x4c, 0xf5, 0xdc, 0xe4, 0x64, 0xd5, 0x16, 0x84, 0x59, 0x6f,
+	0xd6, 0x3c, 0x2c, 0xb6, 0x69, 0x2f, 0xb1, 0xc4, 0x8d, 0x3f, 0xad, 0x0d, 0x58, 0xee, 0x92, 0x77,
+	0x51, 0x6c, 0x01, 0x58, 0xaa, 0xac, 0xa2, 0xd4, 0x82, 0x71, 0x95, 0xd4, 0x07, 0x74, 0x82, 0x8a,
+	0x06, 0xdf, 0x9e, 0x5a, 0x07, 0xf5, 0x1f, 0x25, 0x58, 0xd9, 0xe2, 0x4d, 0x9a, 0xfa, 0xff, 0x1a,
+	0xce, 0xc4, 0xc1, 0x68, 0x12, 0x45, 0xb4, 0x60, 0x65, 0xf5, 0xfa, 0x69, 0xe4, 0xda, 0x64, 0x82,
+	0xba, 0x2b, 0xe8, 0x61, 0xe3, 0x2d, 0xf5, 0xd4, 0x26, 0x55, 0xc4, 0xb1, 0x8c, 0x95, 0x30, 0xab,
+	0xb9, 0x23, 0x56, 0xeb, 0x25, 0x9c, 0x8d, 0x53, 0x21, 0x15, 0x09, 0x3a, 0xa6, 0xff, 0x6b, 0xf9,
+	0x24, 0x76, 0x58, 0x40, 0x9d, 0x2b, 0x86, 0x7c, 0x76, 0x27, 0x25, 0x71, 0x33, 0x3e, 0xeb, 0x09,
+	0x9c, 0xde, 0x65, 0x61, 0x93, 0xef, 0x2e, 0x14, 0xcf, 0x76, 0x26, 0x63, 0xde, 0x88, 0x04, 0x51,
+	0x8c, 0x87, 0xce, 0x9c, 0x61, 0x9f, 0x7e, 0xaa, 0x59, 0x5c, 0xc3, 0x66, 0x7d, 0x1d, 0x85, 0xae,
+	0xa4, 0x43, 0x77, 0x27, 0x77, 0xe8, 0xc6, 0xcc, 0xbd, 0xc8, 0x1b, 0xa8, 0x7f, 0x01, 0xf0, 0xf2,
+	0x98, 0x25, 0xf1, 0xc1, 0xac, 0x57, 0x13, 0x99, 0xcb, 0x39, 0xb6, 0x18, 0xad, 0x13, 0x37, 0x6f,
+	0xcc, 0x9c, 0x49, 0x2b, 0x63, 0x79, 0x7b, 0x0e, 0xcb, 0x4c, 0xd1, 0x40, 0x9a, 0xe7, 0x62, 0xed,
+	0x3c, 0x93, 0x73, 0x2e, 0xa5, 0xd3, 0xba, 0x1f, 0x53, 0xb9, 0x09, 0x63, 0xfd, 0x73, 0x11, 0xc2,
+	0x6d, 0xde, 0xbc, 0xb8, 0x3b, 0xa7, 0xde, 0x9d, 0x00, 0x42, 0x2f, 0x7d, 0x79, 0xa5, 0xb9, 0x3f,
+	0xb7, 0xce, 0xfd, 0x68, 0x67, 0x0e, 0x8d, 0x56, 0xa4, 0x3b, 0x26, 0x50, 0xef, 0x03, 0x38, 0x97,
+	0x0d, 0xe5, 0x1f, 0x04, 0xec, 0xd9, 0xef, 0x01, 0xbb, 0x91, 0xfb, 0x68, 0x59, 0x97, 0x27, 0xe7,
+	0xcb, 0x59, 0xee, 0x0f, 0xec, 0xc2, 0xfe, 0xc0, 0x2e, 0x1c, 0x0c, 0xec, 0xc2, 0xfb, 0xa1, 0x0d,
+	0xfa, 0x43, 0x1b, 0xec, 0x0f, 0x6d, 0x70, 0x30, 0xb4, 0xc1, 0xb7, 0xa1, 0x0d, 0x3e, 0x7d, 0xb7,
+	0x0b, 0x2f, 0xfe, 0x33, 0x7c, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb2, 0xc3, 0x3b, 0x02, 0xf4,
+	0x08, 0x00, 0x00,
+}
+
+func (m *ContainerMetrics) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ContainerMetrics) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ContainerMetrics) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Usage) > 0 {
+		keysForUsage := make([]string, 0, len(m.Usage))
+		for k := range m.Usage {
+			keysForUsage = append(keysForUsage, string(k))
+		}
+		github_com_gogo_protobuf_sortkeys.Strings(keysForUsage)
+		for iNdEx := len(keysForUsage) - 1; iNdEx >= 0; iNdEx-- {
+			v := m.Usage[k8s_io_api_core_v1.ResourceName(keysForUsage[iNdEx])]
+			baseI := i
+			{
+				size, err := ((*resource.Quantity)(&v)).MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintGenerated(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+			i -= len(keysForUsage[iNdEx])
+			copy(dAtA[i:], keysForUsage[iNdEx])
+			i = encodeVarintGenerated(dAtA, i, uint64(len(keysForUsage[iNdEx])))
+			i--
+			dAtA[i] = 0xa
+			i = encodeVarintGenerated(dAtA, i, uint64(baseI-i))
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	i -= len(m.Name)
+	copy(dAtA[i:], m.Name)
+	i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func (m *NodeMetrics) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *NodeMetrics) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *NodeMetrics) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Usage) > 0 {
+		keysForUsage := make([]string, 0, len(m.Usage))
+		for k := range m.Usage {
+			keysForUsage = append(keysForUsage, string(k))
+		}
+		github_com_gogo_protobuf_sortkeys.Strings(keysForUsage)
+		for iNdEx := len(keysForUsage) - 1; iNdEx >= 0; iNdEx-- {
+			v := m.Usage[k8s_io_api_core_v1.ResourceName(keysForUsage[iNdEx])]
+			baseI := i
+			{
+				size, err := ((*resource.Quantity)(&v)).MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintGenerated(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+			i -= len(keysForUsage[iNdEx])
+			copy(dAtA[i:], keysForUsage[iNdEx])
+			i = encodeVarintGenerated(dAtA, i, uint64(len(keysForUsage[iNdEx])))
+			i--
+			dAtA[i] = 0xa
+			i = encodeVarintGenerated(dAtA, i, uint64(baseI-i))
+			i--
+			dAtA[i] = 0x22
+		}
+	}
+	{
+		size, err := m.Window.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x1a
+	{
+		size, err := m.Timestamp.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x12
+	{
+		size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func (m *NodeMetricsList) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *NodeMetricsList) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *NodeMetricsList) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Items) > 0 {
+		for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintGenerated(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	{
+		size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func (m *PodMetrics) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *PodMetrics) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *PodMetrics) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Containers) > 0 {
+		for iNdEx := len(m.Containers) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Containers[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintGenerated(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x22
+		}
+	}
+	{
+		size, err := m.Window.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x1a
+	{
+		size, err := m.Timestamp.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x12
+	{
+		size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func (m *PodMetricsList) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *PodMetricsList) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *PodMetricsList) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Items) > 0 {
+		for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintGenerated(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	{
+		size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintGenerated(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
+	offset -= sovGenerated(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *ContainerMetrics) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Name)
+	n += 1 + l + sovGenerated(uint64(l))
+	if len(m.Usage) > 0 {
+		for k, v := range m.Usage {
+			_ = k
+			_ = v
+			l = ((*resource.Quantity)(&v)).Size()
+			mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l))
+			n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize))
+		}
+	}
+	return n
+}
+
+func (m *NodeMetrics) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.ObjectMeta.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	l = m.Timestamp.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	l = m.Window.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	if len(m.Usage) > 0 {
+		for k, v := range m.Usage {
+			_ = k
+			_ = v
+			l = ((*resource.Quantity)(&v)).Size()
+			mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l))
+			n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize))
+		}
+	}
+	return n
+}
+
+func (m *NodeMetricsList) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.ListMeta.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	if len(m.Items) > 0 {
+		for _, e := range m.Items {
+			l = e.Size()
+			n += 1 + l + sovGenerated(uint64(l))
+		}
+	}
+	return n
+}
+
+func (m *PodMetrics) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.ObjectMeta.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	l = m.Timestamp.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	l = m.Window.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	if len(m.Containers) > 0 {
+		for _, e := range m.Containers {
+			l = e.Size()
+			n += 1 + l + sovGenerated(uint64(l))
+		}
+	}
+	return n
+}
+
+func (m *PodMetricsList) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.ListMeta.Size()
+	n += 1 + l + sovGenerated(uint64(l))
+	if len(m.Items) > 0 {
+		for _, e := range m.Items {
+			l = e.Size()
+			n += 1 + l + sovGenerated(uint64(l))
+		}
+	}
+	return n
+}
+
+func sovGenerated(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozGenerated(x uint64) (n int) {
+	return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (this *ContainerMetrics) String() string {
+	if this == nil {
+		return "nil"
+	}
+	keysForUsage := make([]string, 0, len(this.Usage))
+	for k := range this.Usage {
+		keysForUsage = append(keysForUsage, string(k))
+	}
+	github_com_gogo_protobuf_sortkeys.Strings(keysForUsage)
+	mapStringForUsage := "k8s_io_api_core_v1.ResourceList{"
+	for _, k := range keysForUsage {
+		mapStringForUsage += fmt.Sprintf("%v: %v,", k, this.Usage[k8s_io_api_core_v1.ResourceName(k)])
+	}
+	mapStringForUsage += "}"
+	s := strings.Join([]string{`&ContainerMetrics{`,
+		`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
+		`Usage:` + mapStringForUsage + `,`,
+		`}`,
+	}, "")
+	return s
+}
+func (this *NodeMetrics) String() string {
+	if this == nil {
+		return "nil"
+	}
+	keysForUsage := make([]string, 0, len(this.Usage))
+	for k := range this.Usage {
+		keysForUsage = append(keysForUsage, string(k))
+	}
+	github_com_gogo_protobuf_sortkeys.Strings(keysForUsage)
+	mapStringForUsage := "k8s_io_api_core_v1.ResourceList{"
+	for _, k := range keysForUsage {
+		mapStringForUsage += fmt.Sprintf("%v: %v,", k, this.Usage[k8s_io_api_core_v1.ResourceName(k)])
+	}
+	mapStringForUsage += "}"
+	s := strings.Join([]string{`&NodeMetrics{`,
+		`ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`,
+		`Timestamp:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Timestamp), "Time", "v1.Time", 1), `&`, ``, 1) + `,`,
+		`Window:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Window), "Duration", "v1.Duration", 1), `&`, ``, 1) + `,`,
+		`Usage:` + mapStringForUsage + `,`,
+		`}`,
+	}, "")
+	return s
+}
+func (this *NodeMetricsList) String() string {
+	if this == nil {
+		return "nil"
+	}
+	repeatedStringForItems := "[]NodeMetrics{"
+	for _, f := range this.Items {
+		repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "NodeMetrics", "NodeMetrics", 1), `&`, ``, 1) + ","
+	}
+	repeatedStringForItems += "}"
+	s := strings.Join([]string{`&NodeMetricsList{`,
+		`ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`,
+		`Items:` + repeatedStringForItems + `,`,
+		`}`,
+	}, "")
+	return s
+}
+func (this *PodMetrics) String() string {
+	if this == nil {
+		return "nil"
+	}
+	repeatedStringForContainers := "[]ContainerMetrics{"
+	for _, f := range this.Containers {
+		repeatedStringForContainers += strings.Replace(strings.Replace(f.String(), "ContainerMetrics", "ContainerMetrics", 1), `&`, ``, 1) + ","
+	}
+	repeatedStringForContainers += "}"
+	s := strings.Join([]string{`&PodMetrics{`,
+		`ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`,
+		`Timestamp:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Timestamp), "Time", "v1.Time", 1), `&`, ``, 1) + `,`,
+		`Window:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Window), "Duration", "v1.Duration", 1), `&`, ``, 1) + `,`,
+		`Containers:` + repeatedStringForContainers + `,`,
+		`}`,
+	}, "")
+	return s
+}
+func (this *PodMetricsList) String() string {
+	if this == nil {
+		return "nil"
+	}
+	repeatedStringForItems := "[]PodMetrics{"
+	for _, f := range this.Items {
+		repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "PodMetrics", "PodMetrics", 1), `&`, ``, 1) + ","
+	}
+	repeatedStringForItems += "}"
+	s := strings.Join([]string{`&PodMetricsList{`,
+		`ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`,
+		`Items:` + repeatedStringForItems + `,`,
+		`}`,
+	}, "")
+	return s
+}
+func valueToStringGenerated(v interface{}) string {
+	rv := reflect.ValueOf(v)
+	if rv.IsNil() {
+		return "nil"
+	}
+	pv := reflect.Indirect(rv).Interface()
+	return fmt.Sprintf("*%v", pv)
+}
+func (m *ContainerMetrics) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ContainerMetrics: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ContainerMetrics: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Name = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Usage == nil {
+				m.Usage = make(k8s_io_api_core_v1.ResourceList)
+			}
+			var mapkey k8s_io_api_core_v1.ResourceName
+			mapvalue := &resource.Quantity{}
+			for iNdEx < postIndex {
+				entryPreIndex := iNdEx
+				var wire uint64
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return ErrIntOverflowGenerated
+					}
+					if iNdEx >= l {
+						return io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					wire |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				fieldNum := int32(wire >> 3)
+				if fieldNum == 1 {
+					var stringLenmapkey uint64
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowGenerated
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						stringLenmapkey |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					intStringLenmapkey := int(stringLenmapkey)
+					if intStringLenmapkey < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					postStringIndexmapkey := iNdEx + intStringLenmapkey
+					if postStringIndexmapkey < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					if postStringIndexmapkey > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapkey = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postStringIndexmapkey])
+					iNdEx = postStringIndexmapkey
+				} else if fieldNum == 2 {
+					var mapmsglen int
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowGenerated
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						mapmsglen |= int(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					if mapmsglen < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					postmsgIndex := iNdEx + mapmsglen
+					if postmsgIndex < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					if postmsgIndex > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapvalue = &resource.Quantity{}
+					if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil {
+						return err
+					}
+					iNdEx = postmsgIndex
+				} else {
+					iNdEx = entryPreIndex
+					skippy, err := skipGenerated(dAtA[iNdEx:])
+					if err != nil {
+						return err
+					}
+					if skippy < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					if (iNdEx + skippy) > postIndex {
+						return io.ErrUnexpectedEOF
+					}
+					iNdEx += skippy
+				}
+			}
+			m.Usage[k8s_io_api_core_v1.ResourceName(mapkey)] = ((k8s_io_apimachinery_pkg_api_resource.Quantity)(*mapvalue))
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenerated(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *NodeMetrics) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NodeMetrics: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NodeMetrics: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Timestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Window", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Window.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Usage == nil {
+				m.Usage = make(k8s_io_api_core_v1.ResourceList)
+			}
+			var mapkey k8s_io_api_core_v1.ResourceName
+			mapvalue := &resource.Quantity{}
+			for iNdEx < postIndex {
+				entryPreIndex := iNdEx
+				var wire uint64
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return ErrIntOverflowGenerated
+					}
+					if iNdEx >= l {
+						return io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					wire |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				fieldNum := int32(wire >> 3)
+				if fieldNum == 1 {
+					var stringLenmapkey uint64
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowGenerated
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						stringLenmapkey |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					intStringLenmapkey := int(stringLenmapkey)
+					if intStringLenmapkey < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					postStringIndexmapkey := iNdEx + intStringLenmapkey
+					if postStringIndexmapkey < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					if postStringIndexmapkey > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapkey = k8s_io_api_core_v1.ResourceName(dAtA[iNdEx:postStringIndexmapkey])
+					iNdEx = postStringIndexmapkey
+				} else if fieldNum == 2 {
+					var mapmsglen int
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowGenerated
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						mapmsglen |= int(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					if mapmsglen < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					postmsgIndex := iNdEx + mapmsglen
+					if postmsgIndex < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					if postmsgIndex > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapvalue = &resource.Quantity{}
+					if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil {
+						return err
+					}
+					iNdEx = postmsgIndex
+				} else {
+					iNdEx = entryPreIndex
+					skippy, err := skipGenerated(dAtA[iNdEx:])
+					if err != nil {
+						return err
+					}
+					if skippy < 0 {
+						return ErrInvalidLengthGenerated
+					}
+					if (iNdEx + skippy) > postIndex {
+						return io.ErrUnexpectedEOF
+					}
+					iNdEx += skippy
+				}
+			}
+			m.Usage[k8s_io_api_core_v1.ResourceName(mapkey)] = ((k8s_io_apimachinery_pkg_api_resource.Quantity)(*mapvalue))
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenerated(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *NodeMetricsList) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: NodeMetricsList: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: NodeMetricsList: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Items = append(m.Items, NodeMetrics{})
+			if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenerated(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *PodMetrics) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: PodMetrics: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: PodMetrics: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Timestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Window", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Window.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Containers", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Containers = append(m.Containers, ContainerMetrics{})
+			if err := m.Containers[len(m.Containers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenerated(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *PodMetricsList) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: PodMetricsList: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: PodMetricsList: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Items = append(m.Items, PodMetrics{})
+			if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenerated(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipGenerated(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+			return iNdEx, nil
+		case 1:
+			iNdEx += 8
+			return iNdEx, nil
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthGenerated
+			}
+			iNdEx += length
+			if iNdEx < 0 {
+				return 0, ErrInvalidLengthGenerated
+			}
+			return iNdEx, nil
+		case 3:
+			for {
+				var innerWire uint64
+				var start int = iNdEx
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return 0, ErrIntOverflowGenerated
+					}
+					if iNdEx >= l {
+						return 0, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					innerWire |= (uint64(b) & 0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				innerWireType := int(innerWire & 0x7)
+				if innerWireType == 4 {
+					break
+				}
+				next, err := skipGenerated(dAtA[start:])
+				if err != nil {
+					return 0, err
+				}
+				iNdEx = start + next
+				if iNdEx < 0 {
+					return 0, ErrInvalidLengthGenerated
+				}
+			}
+			return iNdEx, nil
+		case 4:
+			return iNdEx, nil
+		case 5:
+			iNdEx += 4
+			return iNdEx, nil
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+	}
+	panic("unreachable")
+}
+
+var (
+	ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowGenerated   = fmt.Errorf("proto: integer overflow")
+)
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.proto b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.proto
new file mode 100644
index 00000000..50579b10
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/generated.proto
@@ -0,0 +1,89 @@
+/*
+Copyright The Kubernetes Authors.
+
+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.
+*/
+
+
+// This file was autogenerated by go-to-protobuf. Do not edit it manually!
+
+syntax = 'proto2';
+
+package k8s.io.metrics.pkg.apis.metrics.v1beta1;
+
+import "k8s.io/api/core/v1/generated.proto";
+import "k8s.io/apimachinery/pkg/api/resource/generated.proto";
+import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
+import "k8s.io/apimachinery/pkg/runtime/generated.proto";
+import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
+
+// Package-wide variables from generator "generated".
+option go_package = "v1beta1";
+
+// ContainerMetrics sets resource usage metrics of a container.
+message ContainerMetrics {
+  // Container name corresponding to the one from pod.spec.containers.
+  optional string name = 1;
+
+  // The memory usage is the memory working set.
+  map<string, k8s.io.apimachinery.pkg.api.resource.Quantity> usage = 2;
+}
+
+// NodeMetrics sets resource usage metrics of a node.
+message NodeMetrics {
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
+
+  // The following fields define time interval from which metrics were
+  // collected from the interval [Timestamp-Window, Timestamp].
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.Time timestamp = 2;
+
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.Duration window = 3;
+
+  // The memory usage is the memory working set.
+  map<string, k8s.io.apimachinery.pkg.api.resource.Quantity> usage = 4;
+}
+
+// NodeMetricsList is a list of NodeMetrics.
+message NodeMetricsList {
+  // Standard list metadata.
+  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
+
+  // List of node metrics.
+  repeated NodeMetrics items = 2;
+}
+
+// PodMetrics sets resource usage metrics of a pod.
+message PodMetrics {
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
+
+  // The following fields define time interval from which metrics were
+  // collected from the interval [Timestamp-Window, Timestamp].
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.Time timestamp = 2;
+
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.Duration window = 3;
+
+  // Metrics for all containers are collected within the same time window.
+  repeated ContainerMetrics containers = 4;
+}
+
+// PodMetricsList is a list of PodMetrics.
+message PodMetricsList {
+  // Standard list metadata.
+  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+  optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
+
+  // List of pod metrics.
+  repeated PodMetrics items = 2;
+}
+
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/register.go b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/register.go
new file mode 100644
index 00000000..0b9e8615
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/register.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+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.
+*/
+
+package metricsv1beta1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// GroupName is the group name use in this package
+const GroupName = "metrics.k8s.io"
+
+// SchemeGroupVersion is group version used to register these objects
+var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}
+
+// Resource takes an unqualified resource and returns a Group qualified GroupResource
+func Resource(resource string) schema.GroupResource {
+	return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
+
+var (
+	// SchemeBuilder points to a list of functions added to Scheme.
+	SchemeBuilder      = runtime.NewSchemeBuilder(addKnownTypes)
+	localSchemeBuilder = &SchemeBuilder
+	// AddToScheme applies all the stored functions to the scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+)
+
+func addKnownTypes(scheme *runtime.Scheme) error {
+	scheme.AddKnownTypes(SchemeGroupVersion,
+		&NodeMetrics{},
+		&NodeMetricsList{},
+		&PodMetrics{},
+		&PodMetricsList{},
+	)
+	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
+	return nil
+}
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/types.go b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/types.go
new file mode 100644
index 00000000..8bfa52fd
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/types.go
@@ -0,0 +1,95 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+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.
+*/
+
+package metricsv1beta1
+
+import (
+	"k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// +genclient
+// +resourceName=nodes
+// +genclient:readonly
+// +genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// NodeMetrics sets resource usage metrics of a node.
+type NodeMetrics struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+
+	// The following fields define time interval from which metrics were
+	// collected from the interval [Timestamp-Window, Timestamp].
+	Timestamp metav1.Time     `json:"timestamp" protobuf:"bytes,2,opt,name=timestamp"`
+	Window    metav1.Duration `json:"window" protobuf:"bytes,3,opt,name=window"`
+
+	// The memory usage is the memory working set.
+	Usage v1.ResourceList `json:"usage" protobuf:"bytes,4,rep,name=usage,casttype=k8s.io/api/core/v1.ResourceList,castkey=k8s.io/api/core/v1.ResourceName,castvalue=k8s.io/apimachinery/pkg/api/resource.Quantity"`
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// NodeMetricsList is a list of NodeMetrics.
+type NodeMetricsList struct {
+	metav1.TypeMeta `json:",inline"`
+	// Standard list metadata.
+	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+
+	// List of node metrics.
+	Items []NodeMetrics `json:"items" protobuf:"bytes,2,rep,name=items"`
+}
+
+// +genclient
+// +resourceName=pods
+// +genclient:readonly
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// PodMetrics sets resource usage metrics of a pod.
+type PodMetrics struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+
+	// The following fields define time interval from which metrics were
+	// collected from the interval [Timestamp-Window, Timestamp].
+	Timestamp metav1.Time     `json:"timestamp" protobuf:"bytes,2,opt,name=timestamp"`
+	Window    metav1.Duration `json:"window" protobuf:"bytes,3,opt,name=window"`
+
+	// Metrics for all containers are collected within the same time window.
+	Containers []ContainerMetrics `json:"containers" protobuf:"bytes,4,rep,name=containers"`
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// PodMetricsList is a list of PodMetrics.
+type PodMetricsList struct {
+	metav1.TypeMeta `json:",inline"`
+	// Standard list metadata.
+	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+
+	// List of pod metrics.
+	Items []PodMetrics `json:"items" protobuf:"bytes,2,rep,name=items"`
+}
+
+// ContainerMetrics sets resource usage metrics of a container.
+type ContainerMetrics struct {
+	// Container name corresponding to the one from pod.spec.containers.
+	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
+	// The memory usage is the memory working set.
+	Usage v1.ResourceList `json:"usage" protobuf:"bytes,2,rep,name=usage,casttype=k8s.io/api/core/v1.ResourceList,castkey=k8s.io/api/core/v1.ResourceName,castvalue=k8s.io/apimachinery/pkg/api/resource.Quantity"`
+}
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/zz_generated.conversion.go b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/zz_generated.conversion.go
new file mode 100644
index 00000000..ac77072a
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/zz_generated.conversion.go
@@ -0,0 +1,208 @@
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes Authors.
+
+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.
+*/
+
+// Code generated by conversion-gen. DO NOT EDIT.
+
+package metricsv1beta1
+
+import (
+	unsafe "unsafe"
+
+	v1 "k8s.io/api/core/v1"
+	conversion "k8s.io/apimachinery/pkg/conversion"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	metrics "k8s.io/metrics/pkg/apis/metrics"
+)
+
+func init() {
+	localSchemeBuilder.Register(RegisterConversions)
+}
+
+// RegisterConversions adds conversion functions to the given scheme.
+// Public to allow building arbitrary schemes.
+func RegisterConversions(s *runtime.Scheme) error {
+	if err := s.AddGeneratedConversionFunc((*ContainerMetrics)(nil), (*metrics.ContainerMetrics)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_ContainerMetrics_To_metrics_ContainerMetrics(a.(*ContainerMetrics), b.(*metrics.ContainerMetrics), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*metrics.ContainerMetrics)(nil), (*ContainerMetrics)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_metrics_ContainerMetrics_To_v1beta1_ContainerMetrics(a.(*metrics.ContainerMetrics), b.(*ContainerMetrics), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*NodeMetrics)(nil), (*metrics.NodeMetrics)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_NodeMetrics_To_metrics_NodeMetrics(a.(*NodeMetrics), b.(*metrics.NodeMetrics), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*metrics.NodeMetrics)(nil), (*NodeMetrics)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_metrics_NodeMetrics_To_v1beta1_NodeMetrics(a.(*metrics.NodeMetrics), b.(*NodeMetrics), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*NodeMetricsList)(nil), (*metrics.NodeMetricsList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(a.(*NodeMetricsList), b.(*metrics.NodeMetricsList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*metrics.NodeMetricsList)(nil), (*NodeMetricsList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_metrics_NodeMetricsList_To_v1beta1_NodeMetricsList(a.(*metrics.NodeMetricsList), b.(*NodeMetricsList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*PodMetrics)(nil), (*metrics.PodMetrics)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_PodMetrics_To_metrics_PodMetrics(a.(*PodMetrics), b.(*metrics.PodMetrics), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*metrics.PodMetrics)(nil), (*PodMetrics)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_metrics_PodMetrics_To_v1beta1_PodMetrics(a.(*metrics.PodMetrics), b.(*PodMetrics), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*PodMetricsList)(nil), (*metrics.PodMetricsList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(a.(*PodMetricsList), b.(*metrics.PodMetricsList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*metrics.PodMetricsList)(nil), (*PodMetricsList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_metrics_PodMetricsList_To_v1beta1_PodMetricsList(a.(*metrics.PodMetricsList), b.(*PodMetricsList), scope)
+	}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func autoConvert_v1beta1_ContainerMetrics_To_metrics_ContainerMetrics(in *ContainerMetrics, out *metrics.ContainerMetrics, s conversion.Scope) error {
+	out.Name = in.Name
+	out.Usage = *(*v1.ResourceList)(unsafe.Pointer(&in.Usage))
+	return nil
+}
+
+// Convert_v1beta1_ContainerMetrics_To_metrics_ContainerMetrics is an autogenerated conversion function.
+func Convert_v1beta1_ContainerMetrics_To_metrics_ContainerMetrics(in *ContainerMetrics, out *metrics.ContainerMetrics, s conversion.Scope) error {
+	return autoConvert_v1beta1_ContainerMetrics_To_metrics_ContainerMetrics(in, out, s)
+}
+
+func autoConvert_metrics_ContainerMetrics_To_v1beta1_ContainerMetrics(in *metrics.ContainerMetrics, out *ContainerMetrics, s conversion.Scope) error {
+	out.Name = in.Name
+	out.Usage = *(*v1.ResourceList)(unsafe.Pointer(&in.Usage))
+	return nil
+}
+
+// Convert_metrics_ContainerMetrics_To_v1beta1_ContainerMetrics is an autogenerated conversion function.
+func Convert_metrics_ContainerMetrics_To_v1beta1_ContainerMetrics(in *metrics.ContainerMetrics, out *ContainerMetrics, s conversion.Scope) error {
+	return autoConvert_metrics_ContainerMetrics_To_v1beta1_ContainerMetrics(in, out, s)
+}
+
+func autoConvert_v1beta1_NodeMetrics_To_metrics_NodeMetrics(in *NodeMetrics, out *metrics.NodeMetrics, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	out.Timestamp = in.Timestamp
+	out.Window = in.Window
+	out.Usage = *(*v1.ResourceList)(unsafe.Pointer(&in.Usage))
+	return nil
+}
+
+// Convert_v1beta1_NodeMetrics_To_metrics_NodeMetrics is an autogenerated conversion function.
+func Convert_v1beta1_NodeMetrics_To_metrics_NodeMetrics(in *NodeMetrics, out *metrics.NodeMetrics, s conversion.Scope) error {
+	return autoConvert_v1beta1_NodeMetrics_To_metrics_NodeMetrics(in, out, s)
+}
+
+func autoConvert_metrics_NodeMetrics_To_v1beta1_NodeMetrics(in *metrics.NodeMetrics, out *NodeMetrics, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	out.Timestamp = in.Timestamp
+	out.Window = in.Window
+	out.Usage = *(*v1.ResourceList)(unsafe.Pointer(&in.Usage))
+	return nil
+}
+
+// Convert_metrics_NodeMetrics_To_v1beta1_NodeMetrics is an autogenerated conversion function.
+func Convert_metrics_NodeMetrics_To_v1beta1_NodeMetrics(in *metrics.NodeMetrics, out *NodeMetrics, s conversion.Scope) error {
+	return autoConvert_metrics_NodeMetrics_To_v1beta1_NodeMetrics(in, out, s)
+}
+
+func autoConvert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(in *NodeMetricsList, out *metrics.NodeMetricsList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	out.Items = *(*[]metrics.NodeMetrics)(unsafe.Pointer(&in.Items))
+	return nil
+}
+
+// Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList is an autogenerated conversion function.
+func Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(in *NodeMetricsList, out *metrics.NodeMetricsList, s conversion.Scope) error {
+	return autoConvert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(in, out, s)
+}
+
+func autoConvert_metrics_NodeMetricsList_To_v1beta1_NodeMetricsList(in *metrics.NodeMetricsList, out *NodeMetricsList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	out.Items = *(*[]NodeMetrics)(unsafe.Pointer(&in.Items))
+	return nil
+}
+
+// Convert_metrics_NodeMetricsList_To_v1beta1_NodeMetricsList is an autogenerated conversion function.
+func Convert_metrics_NodeMetricsList_To_v1beta1_NodeMetricsList(in *metrics.NodeMetricsList, out *NodeMetricsList, s conversion.Scope) error {
+	return autoConvert_metrics_NodeMetricsList_To_v1beta1_NodeMetricsList(in, out, s)
+}
+
+func autoConvert_v1beta1_PodMetrics_To_metrics_PodMetrics(in *PodMetrics, out *metrics.PodMetrics, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	out.Timestamp = in.Timestamp
+	out.Window = in.Window
+	out.Containers = *(*[]metrics.ContainerMetrics)(unsafe.Pointer(&in.Containers))
+	return nil
+}
+
+// Convert_v1beta1_PodMetrics_To_metrics_PodMetrics is an autogenerated conversion function.
+func Convert_v1beta1_PodMetrics_To_metrics_PodMetrics(in *PodMetrics, out *metrics.PodMetrics, s conversion.Scope) error {
+	return autoConvert_v1beta1_PodMetrics_To_metrics_PodMetrics(in, out, s)
+}
+
+func autoConvert_metrics_PodMetrics_To_v1beta1_PodMetrics(in *metrics.PodMetrics, out *PodMetrics, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	out.Timestamp = in.Timestamp
+	out.Window = in.Window
+	out.Containers = *(*[]ContainerMetrics)(unsafe.Pointer(&in.Containers))
+	return nil
+}
+
+// Convert_metrics_PodMetrics_To_v1beta1_PodMetrics is an autogenerated conversion function.
+func Convert_metrics_PodMetrics_To_v1beta1_PodMetrics(in *metrics.PodMetrics, out *PodMetrics, s conversion.Scope) error {
+	return autoConvert_metrics_PodMetrics_To_v1beta1_PodMetrics(in, out, s)
+}
+
+func autoConvert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(in *PodMetricsList, out *metrics.PodMetricsList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	out.Items = *(*[]metrics.PodMetrics)(unsafe.Pointer(&in.Items))
+	return nil
+}
+
+// Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList is an autogenerated conversion function.
+func Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(in *PodMetricsList, out *metrics.PodMetricsList, s conversion.Scope) error {
+	return autoConvert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(in, out, s)
+}
+
+func autoConvert_metrics_PodMetricsList_To_v1beta1_PodMetricsList(in *metrics.PodMetricsList, out *PodMetricsList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	out.Items = *(*[]PodMetrics)(unsafe.Pointer(&in.Items))
+	return nil
+}
+
+// Convert_metrics_PodMetricsList_To_v1beta1_PodMetricsList is an autogenerated conversion function.
+func Convert_metrics_PodMetricsList_To_v1beta1_PodMetricsList(in *metrics.PodMetricsList, out *PodMetricsList, s conversion.Scope) error {
+	return autoConvert_metrics_PodMetricsList_To_v1beta1_PodMetricsList(in, out, s)
+}
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/zz_generated.deepcopy.go b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/zz_generated.deepcopy.go
new file mode 100644
index 00000000..b8523c1e
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1/zz_generated.deepcopy.go
@@ -0,0 +1,185 @@
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes Authors.
+
+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.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package metricsv1beta1
+
+import (
+	v1 "k8s.io/api/core/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ContainerMetrics) DeepCopyInto(out *ContainerMetrics) {
+	*out = *in
+	if in.Usage != nil {
+		in, out := &in.Usage, &out.Usage
+		*out = make(v1.ResourceList, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val.DeepCopy()
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerMetrics.
+func (in *ContainerMetrics) DeepCopy() *ContainerMetrics {
+	if in == nil {
+		return nil
+	}
+	out := new(ContainerMetrics)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NodeMetrics) DeepCopyInto(out *NodeMetrics) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Timestamp.DeepCopyInto(&out.Timestamp)
+	out.Window = in.Window
+	if in.Usage != nil {
+		in, out := &in.Usage, &out.Usage
+		*out = make(v1.ResourceList, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val.DeepCopy()
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetrics.
+func (in *NodeMetrics) DeepCopy() *NodeMetrics {
+	if in == nil {
+		return nil
+	}
+	out := new(NodeMetrics)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *NodeMetrics) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NodeMetricsList) DeepCopyInto(out *NodeMetricsList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]NodeMetrics, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetricsList.
+func (in *NodeMetricsList) DeepCopy() *NodeMetricsList {
+	if in == nil {
+		return nil
+	}
+	out := new(NodeMetricsList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *NodeMetricsList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PodMetrics) DeepCopyInto(out *PodMetrics) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Timestamp.DeepCopyInto(&out.Timestamp)
+	out.Window = in.Window
+	if in.Containers != nil {
+		in, out := &in.Containers, &out.Containers
+		*out = make([]ContainerMetrics, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodMetrics.
+func (in *PodMetrics) DeepCopy() *PodMetrics {
+	if in == nil {
+		return nil
+	}
+	out := new(PodMetrics)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PodMetrics) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PodMetricsList) DeepCopyInto(out *PodMetricsList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]PodMetrics, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodMetricsList.
+func (in *PodMetricsList) DeepCopy() *PodMetricsList {
+	if in == nil {
+		return nil
+	}
+	out := new(PodMetricsList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PodMetricsList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
diff --git a/vendor/k8s.io/metrics/pkg/apis/metrics/zz_generated.deepcopy.go b/vendor/k8s.io/metrics/pkg/apis/metrics/zz_generated.deepcopy.go
new file mode 100644
index 00000000..c4898788
--- /dev/null
+++ b/vendor/k8s.io/metrics/pkg/apis/metrics/zz_generated.deepcopy.go
@@ -0,0 +1,185 @@
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes Authors.
+
+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.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package metrics
+
+import (
+	v1 "k8s.io/api/core/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ContainerMetrics) DeepCopyInto(out *ContainerMetrics) {
+	*out = *in
+	if in.Usage != nil {
+		in, out := &in.Usage, &out.Usage
+		*out = make(v1.ResourceList, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val.DeepCopy()
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerMetrics.
+func (in *ContainerMetrics) DeepCopy() *ContainerMetrics {
+	if in == nil {
+		return nil
+	}
+	out := new(ContainerMetrics)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NodeMetrics) DeepCopyInto(out *NodeMetrics) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Timestamp.DeepCopyInto(&out.Timestamp)
+	out.Window = in.Window
+	if in.Usage != nil {
+		in, out := &in.Usage, &out.Usage
+		*out = make(v1.ResourceList, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val.DeepCopy()
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetrics.
+func (in *NodeMetrics) DeepCopy() *NodeMetrics {
+	if in == nil {
+		return nil
+	}
+	out := new(NodeMetrics)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *NodeMetrics) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NodeMetricsList) DeepCopyInto(out *NodeMetricsList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]NodeMetrics, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetricsList.
+func (in *NodeMetricsList) DeepCopy() *NodeMetricsList {
+	if in == nil {
+		return nil
+	}
+	out := new(NodeMetricsList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *NodeMetricsList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PodMetrics) DeepCopyInto(out *PodMetrics) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Timestamp.DeepCopyInto(&out.Timestamp)
+	out.Window = in.Window
+	if in.Containers != nil {
+		in, out := &in.Containers, &out.Containers
+		*out = make([]ContainerMetrics, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodMetrics.
+func (in *PodMetrics) DeepCopy() *PodMetrics {
+	if in == nil {
+		return nil
+	}
+	out := new(PodMetrics)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PodMetrics) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PodMetricsList) DeepCopyInto(out *PodMetricsList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]PodMetrics, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodMetricsList.
+func (in *PodMetricsList) DeepCopy() *PodMetricsList {
+	if in == nil {
+		return nil
+	}
+	out := new(PodMetricsList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PodMetricsList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 09839e02..b56ce570 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -212,7 +212,7 @@ golang.org/x/text/unicode/bidi
 golang.org/x/text/unicode/norm
 # golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
 golang.org/x/time/rate
-# golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59
+# golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac
 golang.org/x/tools/go/gcexportdata
 golang.org/x/tools/go/internal/gcimporter
 golang.org/x/tools/go/internal/packagesdriver
@@ -533,6 +533,9 @@ k8s.io/kubernetes/pkg/apis/autoscaling/v1
 k8s.io/kubernetes/pkg/apis/batch
 k8s.io/kubernetes/pkg/apis/core
 k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/duration
+# k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e
+k8s.io/metrics/pkg/apis/metrics
+k8s.io/metrics/pkg/apis/metrics/v1beta1
 # k8s.io/utils v0.0.0-20191010214722-8d271d903fe4
 k8s.io/utils/buffer
 k8s.io/utils/integer
diff --git a/web/src/app/models/content.ts b/web/src/app/models/content.ts
index 2bd6171b..a1f8583d 100644
--- a/web/src/app/models/content.ts
+++ b/web/src/app/models/content.ts
@@ -59,6 +59,18 @@ export interface ContainersView extends View {
   };
 }
 
+export interface DonutChartLabels {
+  plural: string;
+  singular: string;
+}
+
+export interface DonutChartView extends View {
+  config: {
+    segments: DonutSegment[];
+    labels: DonutChartLabels;
+  };
+}
+
 export interface GraphvizView extends View {
   config: {
     dot: string;
@@ -129,6 +141,16 @@ export interface LabelSelectorView extends View {
   };
 }
 
+export interface SingleStatView extends View {
+  config: {
+    title: string;
+    value: {
+      text: string;
+      color: string;
+    };
+  };
+}
+
 export interface PodSummary {
   details: View[];
   status: string;
@@ -277,6 +299,48 @@ export interface TimestampView extends View {
   };
 }
 
+export interface DonutSegment {
+  count: number;
+  status: string;
+}
+
+export interface Series {
+  name: string;
+  value: number;
+  label: string;
+  color: string;
+}
+
+export interface BulletBand {
+  min: number;
+  max: number;
+  color: string;
+  label: string;
+}
+
+export interface Resource {
+  bands: BulletBand[];
+  measure: number;
+  measureLabel: string;
+  label: string;
+}
+
+export interface WorkloadView extends View {
+  config: {
+    name: string;
+    iconName: string;
+    segments: DonutSegment[];
+    memory: Resource;
+    cpu: Resource;
+  };
+}
+
+export interface WorkloadListView extends View {
+  config: {
+    workloads: WorkloadView;
+  };
+}
+
 export interface YAMLView extends View {
   config: {
     data: string;
@@ -341,3 +405,12 @@ export interface IFrameView extends View {
     title: string;
   };
 }
+
+export interface VerticalBulletChartView extends View {
+  config: {
+    bands: BulletBand[];
+    measure: number;
+    measureLabel: string;
+    label: string;
+  };
+}
diff --git a/web/src/app/modules/overview/components/card/card.component.html b/web/src/app/modules/overview/components/card/card.component.html
index 5735901c..34cce8e7 100644
--- a/web/src/app/modules/overview/components/card/card.component.html
+++ b/web/src/app/modules/overview/components/card/card.component.html
@@ -25,9 +25,7 @@
             <h4 class="card-title">{{ title }}</h4>
 
             <div [ngSwitch]="body.metadata.type" class="card-text">
-                <ng-container *ngSwitchCase="'text'">
-                    <app-view-text [view]="body"></app-view-text>
-                </ng-container>
+                <app-content-switcher [view]="body"></app-content-switcher>
             </div>
         </div>
 
diff --git a/web/src/app/modules/overview/components/content-switcher/content-switcher.component.html b/web/src/app/modules/overview/components/content-switcher/content-switcher.component.html
index 4f0f039f..36c0c8cc 100644
--- a/web/src/app/modules/overview/components/content-switcher/content-switcher.component.html
+++ b/web/src/app/modules/overview/components/content-switcher/content-switcher.component.html
@@ -12,6 +12,9 @@
     <ng-container *ngSwitchCase="'containers'">
       <app-view-containers [view]="view"></app-view-containers>
     </ng-container>
+    <ng-container *ngSwitchCase="'donutChart'">
+        <app-view-donut-chart [view]="view"></app-view-donut-chart>
+    </ng-container>
     <ng-container *ngSwitchCase="'expressionSelector'">
       <app-view-expression-selector [view]="view"></app-view-expression-selector>
     </ng-container>
@@ -63,6 +66,9 @@
     <ng-container *ngSwitchCase="'selectors'">
       <app-view-selectors [view]="view"></app-view-selectors>
     </ng-container>
+    <ng-container *ngSwitchCase="'singleStat'">
+      <app-single-stat [view]="view"></app-single-stat>
+    </ng-container>
     <ng-container *ngSwitchCase="'summary'">
       <app-view-summary [view]="view"></app-view-summary>
     </ng-container>
diff --git a/web/src/app/modules/overview/components/donut-chart/donut-chart.component.html b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.html
new file mode 100644
index 00000000..35bd976b
--- /dev/null
+++ b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.html
@@ -0,0 +1,20 @@
+<svg [attr.viewBox]="viewBox()" class="donut" height="100%" width="100%">
+    <circle [attr.cx]="center()" [attr.cy]="center()" [attr.r]="radius()"
+            class="donut-ring"
+            fill="transparent"/>
+
+    <ng-container *ngFor="let descriptor of descriptors(); trackBy: trackByDescriptor">
+        <circle [attr.cx]="center()"
+                [attr.cy]="center()"
+                [attr.r]="radius()"
+                [attr.stroke-dasharray]="descriptor.array"
+                [attr.stroke-dashoffset]="descriptor.offset"
+                [ngClass]="descriptor.className"
+                fill="transparent"/>
+    </ng-container>
+
+    <g class="chart-text">
+        <text x="50%" y="50%" class="chart-number">{{ itemCount() }}</text>
+        <text x="50%" y="50%" class="chart-label">{{ itemLabel() }}</text>
+    </g>
+</svg>
diff --git a/web/src/app/modules/overview/components/donut-chart/donut-chart.component.scss b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.scss
new file mode 100644
index 00000000..5c991af7
--- /dev/null
+++ b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.scss
@@ -0,0 +1,68 @@
+/*!
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+$base-stroke-width: 1px;
+$thick-stroke-width: $base-stroke-width * 2.5;
+
+
+.donut-ring {
+  stroke: #d2d3d4;
+  stroke-width: $base-stroke-width;
+}
+
+.donut-segment {
+  stroke-width: $base-stroke-width;
+
+  &.ok {
+    stroke: #60b515;
+  }
+  &.warning {
+    stroke: #f57600;
+    stroke-width: $thick-stroke-width;
+  }
+  &.error {
+    stroke: #e12200;
+    stroke-width: $thick-stroke-width;
+  }
+}
+
+.chart-text {
+  font: 16px/1em "Metropolis", Arial, sans-serif;
+  fill: #fff;
+  -moz-transform: translateY(0.25em);
+  -ms-transform: translateY(0.25em);
+  -webkit-transform: translateY(0.25em);
+  transform: translateY(0.25em);
+
+  :host-context(body) {
+    --text-color: #575757;
+  }
+  :host-context(body.dark) {
+    --text-color: #acbac3;
+  }
+
+  .chart-number {
+    font-size: 0.6em;
+    fill: var(--text-color);
+    line-height: 1;
+    text-anchor: middle;
+    -moz-transform: translateY(-0.25em);
+    -ms-transform: translateY(-0.25em);
+    -webkit-transform: translateY(-0.25em);
+    transform: translateY(-0.25em);
+  }
+
+  .chart-label {
+    font-size: 0.2em;
+    fill: var(--text-color);
+    text-transform: uppercase;
+    text-anchor: middle;
+    -moz-transform: translateY(0.7em);
+    -ms-transform: translateY(0.7em);
+    -webkit-transform: translateY(0.7em);
+    transform: translateY(0.7em);
+  }
+}
+
diff --git a/web/src/app/modules/overview/components/donut-chart/donut-chart.component.spec.ts b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.spec.ts
new file mode 100644
index 00000000..bc2bd77b
--- /dev/null
+++ b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.spec.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DonutChartComponent } from './donut-chart.component';
+import { DonutChartView } from '../../../../models/content';
+
+describe('DonutChartComponent', () => {
+  let component: DonutChartComponent;
+  let fixture: ComponentFixture<DonutChartComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [DonutChartComponent],
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DonutChartComponent);
+    component = fixture.componentInstance;
+    const view: DonutChartView = {
+      metadata: {
+        type: 'donutChart',
+      },
+      config: {
+        segments: [],
+        labels: {
+          plural: 'items',
+          singular: 'item',
+        },
+      },
+    };
+    component.view = view;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/web/src/app/modules/overview/components/donut-chart/donut-chart.component.ts b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.ts
new file mode 100644
index 00000000..8cb27b95
--- /dev/null
+++ b/web/src/app/modules/overview/components/donut-chart/donut-chart.component.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Component, Input, OnInit } from '@angular/core';
+import { DonutChartView, DonutSegment, View } from '../../../../models/content';
+
+export interface SegmentDescriptor {
+  array: string;
+  className: string;
+  offset: number;
+}
+
+@Component({
+  selector: 'app-view-donut-chart',
+  templateUrl: './donut-chart.component.html',
+  styleUrls: ['./donut-chart.component.scss'],
+})
+export class DonutChartComponent implements OnInit {
+  v: DonutChartView;
+
+  @Input() set view(v: View) {
+    this.v = v as DonutChartView;
+  }
+  get view() {
+    return this.v;
+  }
+
+  @Input() circumference = 100;
+  @Input() height = 42;
+
+  constructor() {}
+
+  ngOnInit() {}
+
+  trackByDescriptor(index: number, item: SegmentDescriptor) {
+    if (!item) {
+      return null;
+    }
+
+    return item.className;
+  }
+
+  radius(): number {
+    return this.circumference / (2 * Math.PI);
+  }
+
+  viewBox(): string {
+    return `0 0 ${this.height} ${this.height}`;
+  }
+
+  center(): number {
+    return this.height / 2;
+  }
+
+  itemCount(): number {
+    if (!this.v) {
+      return 0;
+    }
+    return this.v.config.segments.reduce<number>(
+      (a: number, s: DonutSegment) => a + s.count,
+      0
+    );
+  }
+
+  itemLabel(): string {
+    if (!this.v) {
+      return '';
+    }
+
+    return this.itemCount() > 1
+      ? this.v.config.labels.plural
+      : this.v.config.labels.singular;
+  }
+
+  descriptors(): SegmentDescriptor[] {
+    let offset = 0;
+
+    if (!this.v) {
+      return [];
+    }
+
+    return this.v.config.segments
+      .sort((a, b) => (a.status > b.status ? 1 : -1))
+      .map<SegmentDescriptor>(s => {
+        const x = (s.count / this.itemCount()) * 100;
+
+        const curOffset = offset;
+        offset += 100 - x;
+        return {
+          array: `${x} ${100 - x}`,
+          offset: curOffset,
+          className: `donut-segment ${s.status}`,
+        };
+      });
+  }
+}
diff --git a/web/src/app/modules/overview/components/single-stat/single-stat.component.html b/web/src/app/modules/overview/components/single-stat/single-stat.component.html
new file mode 100644
index 00000000..7cb6363c
--- /dev/null
+++ b/web/src/app/modules/overview/components/single-stat/single-stat.component.html
@@ -0,0 +1,8 @@
+<div class="singleStat">
+    <div class="singleStat-title">
+        {{v.config.title}}
+    </div>
+    <div class="singleStat-body" [style.color]="v.config.value.color">
+        {{v.config.value.text}}
+    </div>
+</div>
diff --git a/web/src/app/modules/overview/components/single-stat/single-stat.component.scss b/web/src/app/modules/overview/components/single-stat/single-stat.component.scss
new file mode 100644
index 00000000..47f9d9d7
--- /dev/null
+++ b/web/src/app/modules/overview/components/single-stat/single-stat.component.scss
@@ -0,0 +1,31 @@
+/*!
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+.singleStat {
+  height: 100%;
+  width: 100%;
+
+  :host-context(body) {
+    --text-color: #575757;
+  }
+
+  :host-context(body.dark) {
+    --text-color: #acbac3;
+  }
+}
+
+.singleStat-title{
+  text-align: center;
+  font-size: 11px;
+  letter-spacing: 0.03em;
+  color: var(--text-color);
+}
+
+.singleStat-body{
+  text-align: center;
+  font-size: 22px;
+  transform: translateY(75%);
+  font-weight: bold;
+}
diff --git a/web/src/app/modules/overview/components/single-stat/single-stat.component.spec.ts b/web/src/app/modules/overview/components/single-stat/single-stat.component.spec.ts
new file mode 100644
index 00000000..5aa2f6e0
--- /dev/null
+++ b/web/src/app/modules/overview/components/single-stat/single-stat.component.spec.ts
@@ -0,0 +1,38 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SingleStatComponent } from './single-stat.component';
+import { SingleStatView } from '../../../../models/content';
+
+describe('SingleStatComponent', () => {
+  let component: SingleStatComponent;
+  let fixture: ComponentFixture<SingleStatComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [SingleStatComponent],
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SingleStatComponent);
+    component = fixture.componentInstance;
+    const view: SingleStatView = {
+      metadata: {
+        type: 'singleStat',
+      },
+      config: {
+        title: 'title',
+        value: {
+          color: 'red',
+          text: 'text',
+        },
+      },
+    };
+    component.view = view;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/web/src/app/modules/overview/components/single-stat/single-stat.component.ts b/web/src/app/modules/overview/components/single-stat/single-stat.component.ts
new file mode 100644
index 00000000..9bf15ebc
--- /dev/null
+++ b/web/src/app/modules/overview/components/single-stat/single-stat.component.ts
@@ -0,0 +1,22 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { SingleStatView, View } from '../../../../models/content';
+
+@Component({
+  selector: 'app-single-stat',
+  templateUrl: './single-stat.component.html',
+  styleUrls: ['./single-stat.component.scss'],
+})
+export class SingleStatComponent implements OnInit {
+  v: SingleStatView;
+
+  @Input() set view(v: View) {
+    this.v = v as SingleStatView;
+  }
+  get view() {
+    return this.v;
+  }
+
+  constructor() {}
+
+  ngOnInit() {}
+}
diff --git a/web/src/app/modules/overview/overview.module.ts b/web/src/app/modules/overview/overview.module.ts
index bedba3bf..7f3ef0ba 100644
--- a/web/src/app/modules/overview/overview.module.ts
+++ b/web/src/app/modules/overview/overview.module.ts
@@ -59,6 +59,8 @@ import { TerminalComponent } from 'src/app/components/terminal/terminal.componen
 import { IFrameComponent } from './components/iframe/iframe.component';
 import { SliderViewComponent } from './components/slider-view/slider-view.component';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DonutChartComponent } from './components/donut-chart/donut-chart.component';
+import { SingleStatComponent } from './components/single-stat/single-stat.component';
 
 export function hljsLanguages() {
   return [{ name: 'yaml', func: yaml }, { name: 'json', func: json }];
@@ -112,6 +114,8 @@ export function hljsLanguages() {
     ContentFilterComponent,
     TerminalComponent,
     SliderViewComponent,
+    DonutChartComponent,
+    SingleStatComponent,
   ],
   imports: [
     BrowserAnimationsModule,


Test patch:
diff --git a/internal/octant/pod_metrics_loader_test.go b/internal/octant/pod_metrics_loader_test.go
new file mode 100644
index 00000000..25105592
--- /dev/null
+++ b/internal/octant/pod_metrics_loader_test.go
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package octant_test
+
+import (
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
+	"github.com/vmware-tanzu/octant/internal/cluster"
+	clusterFake "github.com/vmware-tanzu/octant/internal/cluster/fake"
+	"github.com/vmware-tanzu/octant/internal/gvk"
+	"github.com/vmware-tanzu/octant/internal/octant"
+	octantFake "github.com/vmware-tanzu/octant/internal/octant/fake"
+	"github.com/vmware-tanzu/octant/internal/testutil"
+)
+
+func TestNewClusterPodMetricsLoader(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	clusterClient := clusterFake.NewMockClientInterface(controller)
+
+	tests := []struct {
+		name          string
+		clusterClient cluster.ClientInterface
+		wantErr       bool
+	}{
+		{
+			name:          "with a cluster client",
+			clusterClient: clusterClient,
+		},
+		{
+			name:          "without a cluster client",
+			clusterClient: nil,
+			wantErr:       true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_, err := octant.NewClusterPodMetricsLoader(tt.clusterClient)
+			if tt.wantErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+		})
+	}
+}
+
+func TestClusterPodMetricsLoader_Load(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	clusterClient := clusterFake.NewMockClientInterface(controller)
+
+	m := testutil.ToUnstructured(t, testutil.CreatePodMetrics("pod"))
+
+	tests := []struct {
+		name          string
+		clusterClient cluster.ClientInterface
+		options       []octant.ClusterPodMetricsLoaderOption
+		want          *unstructured.Unstructured
+		wantErr       bool
+	}{
+		{
+			name: "in general",
+			options: []octant.ClusterPodMetricsLoaderOption{
+				func(loader *octant.ClusterPodMetricsLoader) {
+					crud := octantFake.NewMockPodMetricsCRUD(controller)
+					crud.EXPECT().
+						Get("test", "pod").
+						Return(m, nil)
+					loader.PodMetricsCRUD = crud
+				},
+			},
+			clusterClient: clusterClient,
+			want:          m,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			pml, err := octant.NewClusterPodMetricsLoader(tt.clusterClient, tt.options...)
+			require.NoError(t, err)
+
+			got, err := pml.Load("test", "pod")
+
+			if tt.wantErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestClusterPodMetricsLoader_SupportsMetrics(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	tests := []struct {
+		name          string
+		clusterClient cluster.ClientInterface
+		want          bool
+		wantErr       bool
+	}{
+		{
+			name:          "cluster supports pod metrics",
+			clusterClient: initClusterClientWithPodMetrics(controller),
+			want:          true,
+		},
+		{
+			name:          "cluster does not support pod metrics",
+			clusterClient: initClusterClientWithoutPodMetrics(controller),
+			want:          false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			pml, err := octant.NewClusterPodMetricsLoader(tt.clusterClient)
+			require.NoError(t, err)
+
+			got, err := pml.SupportsMetrics()
+			if tt.wantErr {
+				require.Error(t, err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func initClusterClientWithPodMetrics(controller *gomock.Controller) *clusterFake.MockClientInterface {
+	apiResourceLists := []*metav1.APIResourceList{
+		{
+			GroupVersion: gvk.PodMetrics.GroupVersion().String(),
+			APIResources: []metav1.APIResource{
+				{
+					Kind: gvk.PodMetrics.Kind,
+				},
+			},
+		},
+	}
+
+	discoveryClient := clusterFake.NewMockDiscoveryInterface(controller)
+	discoveryClient.EXPECT().ServerPreferredNamespacedResources().Return(apiResourceLists, nil)
+
+	clusterClient := clusterFake.NewMockClientInterface(controller)
+	clusterClient.EXPECT().DiscoveryClient().Return(discoveryClient, nil)
+
+	return clusterClient
+}
+
+func initClusterClientWithoutPodMetrics(controller *gomock.Controller) *clusterFake.MockClientInterface {
+	var apiResourceLists []*metav1.APIResourceList
+
+	discoveryClient := clusterFake.NewMockDiscoveryInterface(controller)
+	discoveryClient.EXPECT().ServerPreferredNamespacedResources().Return(apiResourceLists, nil)
+
+	clusterClient := clusterFake.NewMockClientInterface(controller)
+	clusterClient.EXPECT().DiscoveryClient().Return(discoveryClient, nil)
+
+	return clusterClient
+}
diff --git a/internal/octant/workload_test.go b/internal/octant/workload_test.go
new file mode 100644
index 00000000..033b2a84
--- /dev/null
+++ b/internal/octant/workload_test.go
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2019 VMware, Inc. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package octant_test
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
+
+	"github.com/vmware-tanzu/octant/internal/objectstatus"
+	"github.com/vmware-tanzu/octant/internal/octant"
+	octantFake "github.com/vmware-tanzu/octant/internal/octant/fake"
+	"github.com/vmware-tanzu/octant/internal/testutil"
+	"github.com/vmware-tanzu/octant/pkg/store"
+	storeFake "github.com/vmware-tanzu/octant/pkg/store/fake"
+	"github.com/vmware-tanzu/octant/pkg/view/component"
+)
+
+func podMetricsLoader(controller *gomock.Controller, pm *unstructured.Unstructured, supportsMetrics bool, supportFails bool) *octantFake.MockPodMetricsLoader {
+	pml := octantFake.NewMockPodMetricsLoader(controller)
+
+	var err error
+	if supportFails {
+		err = fmt.Errorf("failed")
+	}
+
+	pml.EXPECT().SupportsMetrics().Return(supportsMetrics, err).AnyTimes()
+
+	if pm != nil {
+		pml.EXPECT().
+			Load("namespace", gomock.Any()).
+			Return(pm, nil).
+			AnyTimes()
+	}
+
+	return pml
+}
+
+func TestClusterWorkloadLoader(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	replicaSet := testutil.ToUnstructured(t, testutil.CreateAppReplicaSet("rs-1"))
+
+	pod1 := testutil.ToUnstructured(t, testutil.CreatePod("pod-1"))
+	pod2 := testutil.ToUnstructured(t, testutil.CreatePod("pod-with-owner", func(pod *corev1.Pod) {
+		pod.OwnerReferences = testutil.ToOwnerReferences(t, replicaSet)
+	}))
+	pods := testutil.ToUnstructuredList(t, pod1, pod2)
+
+	pm := testutil.ToUnstructured(t, testutil.CreatePodMetrics("pm", podMetricWithContainer))
+	rl := corev1.ResourceList{
+		corev1.ResourceCPU:    resource.MustParse("150m"),
+		corev1.ResourceMemory: resource.MustParse("3Mi"),
+	}
+
+	objectStore := storeFake.NewMockStore(controller)
+	objectStore.EXPECT().
+		List(gomock.Any(), store.Key{
+			Namespace:  "namespace",
+			APIVersion: "v1",
+			Kind:       "Pod",
+		}).
+		Return(pods, true, nil).
+		AnyTimes()
+
+	objectStore.EXPECT().
+		Get(gomock.Any(), store.Key{
+			Namespace:  "namespace",
+			APIVersion: "apps/v1",
+			Kind:       "ReplicaSet",
+			Name:       "rs-1",
+		}).
+		Return(replicaSet, true, nil).
+		AnyTimes()
+
+	cases := []struct {
+		name            string
+		namespace       string
+		objectStore     store.Store
+		podMetricLoader octant.PodMetricsLoader
+		expected        []octant.Workload
+		isErr           bool
+	}{
+		{
+			name:            "supports metrics",
+			objectStore:     objectStore,
+			podMetricLoader: podMetricsLoader(controller, pm, true, false),
+			namespace:       "namespace",
+			expected: []octant.Workload{
+				{
+					Name:     "pod-1",
+					IconName: "application",
+					SegmentCounter: map[component.NodeStatus][]octant.PodWithMetric{
+						component.NodeStatusOK: {
+							{
+								Pod:          pod1,
+								ResourceList: rl,
+							},
+						},
+					},
+				},
+				{
+					Name:     "rs-1",
+					IconName: "application",
+					SegmentCounter: map[component.NodeStatus][]octant.PodWithMetric{
+						component.NodeStatusOK: {
+							{
+								Pod:          pod2,
+								ResourceList: rl,
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:            "does not support metrics",
+			objectStore:     objectStore,
+			podMetricLoader: podMetricsLoader(controller, pm, false, false),
+			namespace:       "namespace",
+			expected: []octant.Workload{
+				{
+					Name:     "pod-1",
+					IconName: "application",
+					SegmentCounter: map[component.NodeStatus][]octant.PodWithMetric{
+						component.NodeStatusOK: {
+							{
+								Pod:          pod1,
+								ResourceList: corev1.ResourceList{},
+							},
+						},
+					},
+				},
+				{
+					Name:     "rs-1",
+					IconName: "application",
+					SegmentCounter: map[component.NodeStatus][]octant.PodWithMetric{
+						component.NodeStatusOK: {
+							{
+								Pod:          pod2,
+								ResourceList: corev1.ResourceList{},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:            "namespace is blank",
+			objectStore:     objectStore,
+			podMetricLoader: podMetricsLoader(controller, pm, true, false),
+			isErr:           true,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			ctx := context.Background()
+			wl, err := octant.NewClusterWorkloadLoader(c.objectStore, c.podMetricLoader, func(wl *octant.ClusterWorkloadLoader) {
+				wl.ObjectStatuser = func(context.Context, runtime.Object, store.Store) (status objectstatus.ObjectStatus, e error) {
+					objectStatus := objectstatus.ObjectStatus{}
+					return objectStatus, nil
+
+				}
+			})
+			require.NoError(t, err)
+
+			actual, err := wl.Load(ctx, c.namespace)
+			if c.isErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			testutil.AssertJSONEqual(t, c.expected, actual)
+		})
+	}
+}
+
+func TestWorkloadSummaryChart(t *testing.T) {
+	pwm := octant.PodWithMetric{}
+
+	w := octant.NewWorkload("workload", "icon")
+
+	w.SegmentCounter = map[component.NodeStatus][]octant.PodWithMetric{
+		component.NodeStatusOK:      {pwm},
+		component.NodeStatusWarning: {pwm, pwm},
+		component.NodeStatusError:   {pwm, pwm, pwm},
+	}
+
+	cases := []struct {
+		name     string
+		workload *octant.Workload
+		expected *component.DonutChart
+		isErr    bool
+	}{
+		{
+			name:     "in general",
+			workload: w,
+			expected: &component.DonutChart{
+				Config: component.DonutChartConfig{
+					Segments: []component.DonutSegment{
+						{
+							Count:  3,
+							Status: component.NodeStatusError,
+						},
+						{
+							Count:  1,
+							Status: component.NodeStatusOK,
+						},
+						{
+							Count:  2,
+							Status: component.NodeStatusWarning,
+						},
+					},
+					Labels: component.DonutChartLabels{
+						Plural:   "Pods",
+						Singular: "Pod",
+					},
+				},
+			},
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			actual, err := c.workload.DonutChart()
+			if c.isErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			component.AssertEqual(t, c.expected, actual)
+		})
+	}
+}
+
+// nolint:dupl
+func TestPodCPUStat(t *testing.T) {
+	expectedTitle := "CPU(cores)"
+
+	cases := []struct {
+		name     string
+		workload *octant.Workload
+		expected *component.SingleStat
+		isErr    bool
+	}{
+		{
+			name: "pod with limit and request; measure below request",
+			workload: createWorkload(t, "10m", "1Mi",
+				podOptionWithLimitAndRequestValues(
+					"500m", "14Mi", "250m", "7Mi")),
+			expected: component.NewSingleStat(expectedTitle, "30m", octant.WorkloadStatusColorOK),
+		},
+		{
+			name: "pod with limit and request; measure below limit",
+			workload: createWorkload(t, "400m", "1Mi",
+				podOptionWithLimitAndRequestValues(
+					"500m", "14Mi", "250m", "7Mi")),
+			expected: component.NewSingleStat(expectedTitle, "1200m", octant.WorkloadStatusColorWarning),
+		},
+		{
+			name: "pod with limit and request; measure above limit",
+			workload: createWorkload(t, "501m", "1Mi",
+				podOptionWithLimitAndRequestValues(
+					"500m", "14Mi", "250m", "7Mi")),
+			expected: component.NewSingleStat(expectedTitle, "1503m", octant.WorkloadStatusColorError),
+		},
+		{
+			name: "pod with limit; measure below limit",
+			workload: createWorkload(t, "10m", "1Mi",
+				podOptionWithLimitValues("500m", "14Mi")),
+			expected: component.NewSingleStat(expectedTitle, "30m", octant.WorkloadStatusColorOK),
+		},
+		{
+			name: "pod with request; measure below above request",
+			workload: createWorkload(t, "501m", "1Mi",
+				podOptionWithRequestValues("500m", "14Mi")),
+			expected: component.NewSingleStat(expectedTitle, "1503m", octant.WorkloadStatusColorWarning),
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			actual, err := octant.PodCPUStat(c.workload)
+			if c.isErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+
+			component.AssertEqual(t, c.expected, actual)
+		})
+	}
+}
+
+// nolint:dupl
+func TestPodMemoryStat(t *testing.T) {
+	expectedTitle := "Memory(bytes)"
+
+	cases := []struct {
+		name     string
+		workload *octant.Workload
+		expected *component.SingleStat
+		isErr    bool
+	}{
+		{
+			name: "pod with limit and request; measure below request",
+			workload: createWorkload(t, "10m", "1Mi",
+				podOptionWithLimitAndRequestValues(
+					"500m", "14Mi", "250m", "7Mi")),
+			expected: component.NewSingleStat(expectedTitle, "3Mi", octant.WorkloadStatusColorOK),
+		},
+		{
+			name: "pod with limit and request; measure below limit, but above request",
+			workload: createWorkload(t, "400m", "8Mi",
+				podOptionWithLimitAndRequestValues(
+					"500m", "14Mi", "250m", "7Mi")),
+			expected: component.NewSingleStat(expectedTitle, "24Mi", octant.WorkloadStatusColorWarning),
+		},
+		{
+			name: "pod with limit and request; measure above limit",
+			workload: createWorkload(t, "501m", "15Mi",
+				podOptionWithLimitAndRequestValues(
+					"500m", "14Mi", "250m", "7Mi")),
+			expected: component.NewSingleStat(expectedTitle, "45Mi", octant.WorkloadStatusColorError),
+		},
+		{
+			name: "pod with limit; measure below limit",
+			workload: createWorkload(t, "10m", "1Mi",
+				podOptionWithLimitValues("500m", "14Mi")),
+			expected: component.NewSingleStat(expectedTitle, "3Mi", octant.WorkloadStatusColorOK),
+		},
+		{
+			name: "pod with request; measure below above request",
+			workload: createWorkload(t, "501m", "15Mi",
+				podOptionWithRequestValues("500m", "14Mi")),
+			expected: component.NewSingleStat(expectedTitle, "45Mi", octant.WorkloadStatusColorWarning),
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			actual, err := octant.PodMemoryStat(c.workload)
+			if c.isErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+
+			component.AssertEqual(t, c.expected, actual)
+		})
+	}
+}
+
+func TestCombineResourceRequirements(t *testing.T) {
+	a := corev1.ResourceRequirements{
+		Requests: corev1.ResourceList{
+			corev1.ResourceCPU:    resource.MustParse("25m"),
+			corev1.ResourceMemory: resource.MustParse("1Mi"),
+		},
+		Limits: corev1.ResourceList{
+			corev1.ResourceCPU:    resource.MustParse("50m"),
+			corev1.ResourceMemory: resource.MustParse("2Mi"),
+		},
+	}
+
+	actual := octant.CombineResourceRequirements(a, a)
+
+	assert.Equal(t, "100m", actual.Limits.Cpu().String())
+	assert.Equal(t, "4Mi", actual.Limits.Memory().String())
+	assert.Equal(t, "50m", actual.Requests.Cpu().String())
+	assert.Equal(t, "2Mi", actual.Requests.Memory().String())
+}
+
+func createWorkload(t *testing.T, cpu, memory string, options ...testutil.PodOption) *octant.Workload {
+	resourceList := createResourceList(cpu, memory)
+	pod := testutil.ToUnstructured(t, testutil.CreatePod("pod", options...))
+
+	workload := octant.NewWorkload("workload", "application")
+
+	workload.AddPodStatus(component.NodeStatusOK, pod, resourceList.DeepCopy())
+	workload.AddPodStatus(component.NodeStatusWarning, pod, resourceList.DeepCopy())
+	workload.AddPodStatus(component.NodeStatusError, pod, resourceList.DeepCopy())
+
+	return workload
+}
+
+func podOptionWithLimitAndRequestValues(limitCPU, limitMemory, requestCPU, requestMemory string) testutil.PodOption {
+	return func(pod *corev1.Pod) {
+		podOptionWithLimitValues(limitCPU, limitMemory)(pod)
+		podOptionWithRequestValues(requestCPU, requestMemory)(pod)
+	}
+}
+
+func podOptionWithLimitValues(cpu, memory string) testutil.PodOption {
+	return func(pod *corev1.Pod) {
+		if len(pod.Spec.Containers) != 1 {
+			pod.Spec.Containers = []corev1.Container{{Name: "container"}}
+		}
+
+		pod.Spec.Containers[0].Resources.Limits = createResourceList(cpu, memory)
+	}
+}
+func podOptionWithRequestValues(cpu, memory string) testutil.PodOption {
+	return func(pod *corev1.Pod) {
+		if len(pod.Spec.Containers) != 1 {
+			pod.Spec.Containers = []corev1.Container{{Name: "container"}}
+		}
+
+		pod.Spec.Containers[0].Resources.Requests = createResourceList(cpu, memory)
+	}
+}
+
+func createResourceList(cpu, memory string) corev1.ResourceList {
+	return corev1.ResourceList{
+		corev1.ResourceCPU:    resource.MustParse(cpu),
+		corev1.ResourceMemory: resource.MustParse(memory),
+	}
+}
+
+func podMetricWithContainer(podMetric *metricsv1beta1.PodMetrics) {
+	podMetric.Containers = []metricsv1beta1.ContainerMetrics{
+		{
+			Name: "container",
+			Usage: corev1.ResourceList{
+				corev1.ResourceCPU:    resource.MustParse("150m"),
+				corev1.ResourceMemory: resource.MustParse("3Mi"),
+			},
+		},
+	}
+}
diff --git a/internal/testutil/generator.go b/internal/testutil/generator.go
index 7c02a4c6..8dba0a5a 100644
--- a/internal/testutil/generator.go
+++ b/internal/testutil/generator.go
@@ -8,6 +8,8 @@ package testutil
 import (
 	appsv1 "k8s.io/api/apps/v1"
 	autoscalingv1 "k8s.io/api/autoscaling/v1"
+	batchv1 "k8s.io/api/batch/v1"
+	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
 	extv1beta1 "k8s.io/api/extensions/v1beta1"
 	rbacv1 "k8s.io/api/rbac/v1"
@@ -18,9 +20,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/intstr"
-
-	batchv1 "k8s.io/api/batch/v1"
-	batchv1beta1 "k8s.io/api/batch/v1beta1"
+	metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
 
 	"github.com/vmware-tanzu/octant/internal/conversion"
 	"github.com/vmware-tanzu/octant/internal/gvk"
@@ -233,6 +233,21 @@ func CreatePod(name string, options ...PodOption) *corev1.Pod {
 	return pod
 }
 
+type PodMetricOption func(metrics *metricsv1beta1.PodMetrics)
+
+func CreatePodMetrics(name string, options ...PodMetricOption) *metricsv1beta1.PodMetrics {
+	m := &metricsv1beta1.PodMetrics{
+		TypeMeta:   genTypeMeta(gvk.PodMetrics),
+		ObjectMeta: genObjectMeta(name, true),
+	}
+
+	for _, option := range options {
+		option(m)
+	}
+
+	return m
+}
+
 // CreateReplicationController creates a replication controller
 func CreateReplicationController(name string) *corev1.ReplicationController {
 	return &corev1.ReplicationController{
diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go
index 91ea2c58..c36eabb2 100644
--- a/internal/testutil/testutil.go
+++ b/internal/testutil/testutil.go
@@ -79,7 +79,7 @@ func ToOwnerReferences(t *testing.T, object runtime.Object) []metav1.OwnerRefere
 	}
 }
 
-//Time generates a test time
+// Time generates a test time
 func Time() time.Time {
 	return time.Unix(1547211430, 0)
 }
diff --git a/pkg/view/component/card_test.go b/pkg/view/component/card_test.go
index bef03deb..c2a1f04f 100644
--- a/pkg/view/component/card_test.go
+++ b/pkg/view/component/card_test.go
@@ -5,7 +5,7 @@ import (
 )
 
 func TestCard_SetAlert(t *testing.T) {
-	card := NewCard("card")
+	card := NewCard(TitleFromString("card"))
 
 	alert := Alert{
 		Type:    AlertTypeError,
@@ -14,14 +14,14 @@ func TestCard_SetAlert(t *testing.T) {
 
 	card.SetAlert(alert)
 
-	expected := NewCard("card")
+	expected := NewCard(TitleFromString("card"))
 	expected.Config.Alert = &alert
 
 	AssertEqual(t, expected, card)
 }
 
 func TestCard_AddAction(t *testing.T) {
-	card := NewCard("card")
+	card := NewCard(TitleFromString("card"))
 
 	action := Action{
 		Name:  "action",
@@ -31,27 +31,27 @@ func TestCard_AddAction(t *testing.T) {
 
 	card.AddAction(action)
 
-	expected := NewCard("card")
+	expected := NewCard(TitleFromString("card"))
 	expected.Config.Actions = []Action{action}
 
 	AssertEqual(t, expected, card)
 }
 
 func TestCard_SetBody(t *testing.T) {
-	card := NewCard("card")
+	card := NewCard(TitleFromString("card"))
 
 	body := NewText("body")
 
 	card.SetBody(body)
 
-	expected := NewCard("card")
+	expected := NewCard(TitleFromString("card"))
 	expected.Config.Body = body
 
 	AssertEqual(t, expected, card)
 }
 
 func TestCardList_AddCard(t *testing.T) {
-	card := NewCard("card")
+	card := NewCard(TitleFromString("card"))
 
 	cardList := NewCardList("list")
 	cardList.AddCard(*card)