Skip to content
This repository has been archived by the owner on Mar 17, 2021. It is now read-only.

Commit

Permalink
First attempt at showing memory
Browse files Browse the repository at this point in the history
We seem to hit an int overflow somewhere when printing out the data sizes, and
the first columen has a tendency to waste a lot of screen space.

concourse/concourse#6140

Signed-off-by: Jamie Klassen <[email protected]>
Co-authored-by: Zoe Tian <[email protected]>
  • Loading branch information
Jamie Klassen and Zoe Tian committed Oct 8, 2020
1 parent c231c1b commit 0bf4de4
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 13 deletions.
27 changes: 23 additions & 4 deletions accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,28 @@ func printSamples(writer io.Writer, samples []Sample) error {
workloads = append(workloads, w.ToString())
}
data = append(data, ui.TableRow{
ui.TableCell{Contents: sample.Container.Handle},
ui.TableCell{Contents: string(sample.Labels.Type)},
ui.TableCell{Contents: strings.Join(workloads, ",")},
ui.TableCell{Contents: string(sample.Labels.Type)},
ui.TableCell{Contents: humanReadable(sample.Container.Stats.Memory)},
ui.TableCell{Contents: sample.Container.Handle},
})
}
table := ui.Table{
Headers: ui.TableRow{
ui.TableCell{
Contents: "handle",
Contents: "workloads",
Color: color.New(color.Bold),
},
ui.TableCell{
Contents: "type",
Color: color.New(color.Bold),
},
ui.TableCell{
Contents: "workloads",
Contents: "memory",
Color: color.New(color.Bold),
},
ui.TableCell{
Contents: "handle",
Color: color.New(color.Bold),
},
},
Expand All @@ -136,6 +141,19 @@ func printSamples(writer io.Writer, samples []Sample) error {
return table.Render(writer, true)
}

func humanReadable(bytes uint64) string {
KB := uint64(1) << 10
MB := KB << 10
switch {
case bytes > MB:
return fmt.Sprintf("%.1f MB", float64(bytes / MB) + float64(bytes % MB)/float64(MB))
case bytes > KB:
return fmt.Sprintf("%.1f KB", float64(bytes / KB) + float64(bytes % KB)/float64(KB))
default:
return fmt.Sprintf("%d B", bytes)
}
}

type Sample struct {
Container Container
Labels Labels
Expand All @@ -158,6 +176,7 @@ type Container struct {
}

type Stats struct {
Memory uint64
}

// a Workload is a description of a concourse core concept that corresponds to
Expand Down
3 changes: 3 additions & 0 deletions accounts/accounts_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ func TestAccounts(t *testing.T) {
suite.Run(t, &K8sClientSuite{
Assertions: require.New(t),
})
suite.Run(t, &GardenConnectionSuite{
Assertions: require.New(t),
})
}
67 changes: 67 additions & 0 deletions accounts/garden_connection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package accounts_test

import (
"net"

"code.cloudfoundry.org/garden"
"code.cloudfoundry.org/garden/gardenfakes"
"code.cloudfoundry.org/garden/server"
"code.cloudfoundry.org/lager/lagertest"
"github.com/concourse/ft/accounts"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

type GardenConnectionSuite struct {
suite.Suite
*require.Assertions
gardenServer *server.GardenServer
backend *gardenfakes.FakeBackend
listener net.Listener
}

func (s *GardenConnectionSuite) SetupTest() {
s.backend = new(gardenfakes.FakeBackend)
s.gardenServer = server.New(
"tcp",
"127.0.0.1:7777",
0,
s.backend,
lagertest.NewTestLogger("test"),
)
s.listener, _ = net.Listen("tcp", "127.0.0.1:7777")
go s.gardenServer.Serve(s.listener)
}

func (s *GardenConnectionSuite) TearDownTest() {
s.gardenServer.Stop()
s.listener.Close()
}

func (s *LANWorkerSuite) TestGetsAllMetrics() {
metricsEntry := garden.ContainerMetricsEntry{
Metrics: garden.Metrics{
MemoryStat: garden.ContainerMemoryStat{
TotalActiveAnon: 123,
},
},
Err: nil,
}
s.backend.BulkMetricsReturns(map[string]garden.ContainerMetricsEntry{
"container-handle": metricsEntry,
}, nil)

connection := accounts.GardenConnection{
Dialer: &accounts.LANGardenDialer{},
}
metricsEntries, err := connection.AllMetrics()

s.NoError(err)
s.Len(metricsEntries, 1)
s.Contains(metricsEntries, "container-handle")
s.Equal(metricsEntry, metricsEntries["container-handle"])
}

// a container "exists" if it appears in the output from List
// BulkMetrics fails if you ask about nonexistent containers
// BulkMetrics actually returns metrics for the containers you asked about
33 changes: 27 additions & 6 deletions accounts/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"time"

"code.cloudfoundry.org/garden"
"code.cloudfoundry.org/garden/client/connection"
"code.cloudfoundry.org/lager"
v1 "k8s.io/api/core/v1"
Expand All @@ -21,19 +22,39 @@ type GardenWorker struct {
Dialer GardenDialer
}

func (gw *GardenWorker) Containers(opts ...StatsOption) ([]Container, error) {
handles, err := connection.NewWithDialerAndLogger(
type GardenConnection struct {
Dialer GardenDialer
}

func (gc GardenConnection) AllMetrics() (map[string]garden.ContainerMetricsEntry, error) {
connection := connection.NewWithDialerAndLogger(
func(string, string) (net.Conn, error) {
return gw.Dialer.Dial()
return gc.Dialer.Dial()
},
lager.NewLogger("garden-connection"),
).List(nil)
)
handles, err := connection.List(nil)
if err != nil {
return nil, err
}
return connection.BulkMetrics(handles)
}

func (gw *GardenWorker) Containers(opts ...StatsOption) ([]Container, error) {
metricsEntries, err := GardenConnection{Dialer: gw.Dialer}.AllMetrics()
if err != nil {
return nil, err
}
containers := []Container{}
for _, handle := range handles {
containers = append(containers, Container{Handle: handle})
for handle, metricsEntry := range metricsEntries {
memory := metricsEntry.Metrics.MemoryStat.TotalUsageTowardLimit
containers = append(
containers,
Container{
Handle: handle,
Stats: Stats{Memory: memory},
},
)
}
return containers, nil
}
Expand Down
25 changes: 22 additions & 3 deletions accounts/worker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
)

// TODO replace real garden server with fake gardenconnection

type LANWorkerSuite struct {
suite.Suite
*require.Assertions
Expand Down Expand Up @@ -52,9 +54,25 @@ func (s *LANWorkerSuite) TearDownTest() {
}

func (s *LANWorkerSuite) TestLANWorkerListsContainers() {
fakeContainer := new(gardenfakes.FakeContainer)
fakeContainer.HandleReturns("container-handle")
s.backend.ContainersReturns([]garden.Container{fakeContainer}, nil)
worker := accounts.GardenWorker{
Dialer: &accounts.LANGardenDialer{},
}
worker.Containers()

s.Equal(s.backend.ContainersCallCount(), 1)
}

func (s *LANWorkerSuite) TestLANWorkerGetsMemory() {
s.backend.BulkMetricsReturns(map[string]garden.ContainerMetricsEntry{
"container-handle": garden.ContainerMetricsEntry{
Metrics: garden.Metrics{
MemoryStat: garden.ContainerMemoryStat{
TotalUsageTowardLimit: 123,
},
},
Err: nil,
},
}, nil)

worker := accounts.GardenWorker{
Dialer: &accounts.LANGardenDialer{},
Expand All @@ -64,6 +82,7 @@ func (s *LANWorkerSuite) TestLANWorkerListsContainers() {
s.NoError(err)
s.Len(containers, 1)
s.Equal(containers[0].Handle, "container-handle")
s.Equal(uint64(123), containers[0].Stats.Memory)
}

type K8sGardenDialerSuite struct {
Expand Down

0 comments on commit 0bf4de4

Please sign in to comment.