Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sparklines #622

Merged
merged 4 commits into from
Nov 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ DOCKER_DISTRIB=docker/docker-$(DOCKER_VERSION).tgz
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(DOCKER_VERSION).tgz
RUNSVINIT=vendor/runsvinit/runsvinit
RM=--rm
RUN_FLAGS=-ti
BUILD_IN_CONTAINER=true

all: $(SCOPE_EXPORT)
Expand All @@ -43,7 +44,7 @@ $(PROBE_EXE): probe/*.go probe/docker/*.go probe/kubernetes/*.go probe/endpoint/

ifeq ($(BUILD_IN_CONTAINER),true)
$(APP_EXE) $(PROBE_EXE) $(RUNSVINIT): $(SCOPE_BACKEND_BUILD_UPTODATE)
$(SUDO) docker run $(RM) -v $(shell pwd):/go/src/github.com/weaveworks/scope -e GOARCH -e GOOS \
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd):/go/src/github.com/weaveworks/scope -e GOARCH -e GOOS \
$(SCOPE_BACKEND_BUILD_IMAGE) SCOPE_VERSION=$(SCOPE_VERSION) $@
else
$(APP_EXE) $(PROBE_EXE): $(SCOPE_BACKEND_BUILD_UPTODATE)
Expand All @@ -67,22 +68,22 @@ static: client/build/app.js
ifeq ($(BUILD_IN_CONTAINER),true)
client/build/app.js: $(shell find client/app/scripts -type f)
mkdir -p client/build
$(SUDO) docker run $(RM) -v $(shell pwd)/client/app:/home/weave/app \
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/build:/home/weave/build \
$(SCOPE_UI_BUILD_IMAGE) npm run build

client-test: $(shell find client/app/scripts -type f)
$(SUDO) docker run $(RM) -v $(shell pwd)/client/app:/home/weave/app \
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/test:/home/weave/test \
$(SCOPE_UI_BUILD_IMAGE) npm test

client-lint:
$(SUDO) docker run $(RM) -v $(shell pwd)/client/app:/home/weave/app \
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/test:/home/weave/test \
$(SCOPE_UI_BUILD_IMAGE) npm run lint

client-start:
$(SUDO) docker run $(RM) --net=host -v $(shell pwd)/client/app:/home/weave/app \
$(SUDO) docker run $(RM) $(RUN_FLAGS) --net=host -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/build:/home/weave/build \
$(SCOPE_UI_BUILD_IMAGE) npm start
endif
Expand All @@ -105,7 +106,7 @@ clean:

ifeq ($(BUILD_IN_CONTAINER),true)
tests: $(SCOPE_BACKEND_BUILD_UPTODATE)
$(SUDO) docker run $(RM) -v $(shell pwd):/go/src/github.com/weaveworks/scope \
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd):/go/src/github.com/weaveworks/scope \
-e GOARCH -e GOOS -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR\
$(SCOPE_BACKEND_BUILD_IMAGE) tests
else
Expand Down
9,240 changes: 4,624 additions & 4,616 deletions app/static.go

Large diffs are not rendered by default.

17 changes: 7 additions & 10 deletions client/app/scripts/components/node-details-table.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
const React = require('react');
const Sparkline = require('./sparkline');

const NodeDetailsTable = React.createClass({

render: function() {
const isNumeric = this.props.isNumeric;

return (
<div className="node-details-table">
<h4 className="node-details-table-title truncate" title={this.props.title}>
Expand All @@ -15,14 +14,12 @@ const NodeDetailsTable = React.createClass({
return (
<div className="node-details-table-row" key={row.key + row.value_major}>
<div className="node-details-table-row-key truncate" title={row.key}>{row.key}</div>
{isNumeric && <div className="node-details-table-row-value-scalar">{row.value_major}</div>}
{isNumeric && <div className="node-details-table-row-value-unit">{row.value_minor}</div>}
{!isNumeric && <div className="node-details-table-row-value-major truncate" title={row.value_major}>
{row.value_major}
</div>}
{!isNumeric && row.value_minor && <div className="node-details-table-row-value-minor truncate" title={row.value_minor}>
{row.value_minor}
</div>}
{ row.value_type === 'numeric' && <div className="node-details-table-row-value-scalar">{row.value_major}</div> }
{ row.value_type === 'numeric' && <div className="node-details-table-row-value-unit">{row.value_minor}</div> }
{ row.value_type === 'sparkline' && <div className="node-details-table-row-value-sparkline"><Sparkline data={row.metric.samples} min={0} max={row.metric.max} first={row.metric.first} last={row.metric.last} interpolate="none" />{row.value_major}</div> }
{ row.value_type === 'sparkline' && <div className="node-details-table-row-value-unit">{row.value_minor}</div> }
{ row.value_type !== 'numeric' && row.value_type !== 'sparkline' && <div className="node-details-table-row-value-major truncate" title={row.value_major}>{row.value_major}</div> }
{ row.value_type !== 'numeric' && row.value_type !== 'sparkline' && row.value_minor && <div className="node-details-table-row-value-minor truncate" title={row.value_minor}>{row.value_minor}</div> }

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

</div>
);
})}
Expand Down
129 changes: 129 additions & 0 deletions client/app/scripts/components/sparkline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Forked from: https://github.com/KyleAMathews/react-sparkline at commit a9d7c5203d8f240938b9f2288287aaf0478df013
const React = require('react');
const d3 = require('d3');

const Sparkline = React.createClass({
getDefaultProps: function() {
return {
width: 100,
height: 16,
strokeColor: '#7d7da8',
strokeWidth: '0.5px',
interpolate: 'basis',
circleDiameter: 1.75,
data: [1, 23, 5, 5, 23, 0, 0, 0, 4, 32, 3, 12, 3, 1, 24, 1, 5, 5, 24, 23] // Some semi-random data.
};
},

componentDidMount: function() {
return this.renderSparkline();
},

renderSparkline: function() {
// If the sparkline has already been rendered, remove it.
let el = this.getDOMNode();
while (el.firstChild) {
el.removeChild(el.firstChild);
}

let data = this.props.data.slice();

// Do nothing if no data is passed in.
if (data.length === 0) {
return;
}

let x = d3.scale.linear().range([2, this.props.width - 2]);
let y = d3.scale.linear().range([this.props.height - 2, 2]);

// react-sparkline allows you to pass in two types of data.
// Data tied to dates and linear data. We need to change our line and x/y
// functions depending on the type of data.

// These are objects with a date key
let line;
let lastX;
let lastY;
let title;
if (data[0].date) {
// Convert dates into D3 dates
data.forEach(d => {
d.date = d3.time.format.iso.parse(d.date);
});

line = d3.svg.line().
interpolate(this.props.interpolate).
x(d => x(d.date)).
y(d => y(d.value));

let first = this.props.first ? d3.time.format.iso.parse(this.props.first) : d3.min(data, d => d.date);
let last = this.props.last ? d3.time.format.iso.parse(this.props.last) : d3.max(data, d => d.date);
x.domain([first, last]);

y.domain([
this.props.min || d3.min(data, d => d.value),
this.props.max || d3.max(data, d => d.value)
]);

lastX = x(data[data.length - 1].date);
lastY = y(data[data.length - 1].value);
title = 'Last ' + d3.round((last - first) / 1000) + ' seconds, ' + data.length + ' samples, min: ' + d3.round(d3.min(data, d => d.value), 2) + ', max: ' + d3.round(d3.max(data, d => d.value), 2) + ', mean: ' + d3.round(d3.mean(data, d => d.value), 2);
} else {
line = d3.svg.line().
interpolate(this.props.interpolate).
x((d, i) => x(i)).
y(d => y(d));

x.domain([
this.props.first || 0,
this.props.last || data.length
]);

y.domain([
this.props.min || d3.min(data),
this.props.max || d3.max(data)
]);

lastX = x(data.length - 1);
lastY = y(data[data.length - 1]);
title = data.length + ' samples, min: ' + d3.round(d3.min(data), 2) + ', max: ' + d3.round(d3.max(data), 2) + ', mean: ' + d3.round(d3.mean(data), 2);
}

d3.select(this.getDOMNode()).attr('title', title);

let svg = d3.select(this.getDOMNode()).

This comment was marked as abuse.

This comment was marked as abuse.

append('svg').
attr('width', this.props.width).
attr('height', this.props.height).
append('g');

svg.append('path').
datum(data).
attr('class', 'sparkline').
style('fill', 'none').
style('stroke', this.props.strokeColor).
style('stroke-width', this.props.strokeWidth).
attr('d', line);

svg.append('circle').
attr('class', 'sparkcircle').
attr('cx', lastX).
attr('cy', lastY).
attr('fill', '#46466a').
attr('fill-opacity', 0.6).
attr('stroke', 'none').
attr('r', this.props.circleDiameter);
},

render: function() {
return (
<div/>
);
},

componentDidUpdate: function() {
return this.renderSparkline();
}
});

module.exports = Sparkline;
9 changes: 9 additions & 0 deletions client/app/styles/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,15 @@ h2 {
color: @text-secondary-color;
}

&-value-sparkline {
> div {
display: inline-block;
}
span {
margin-left: 1em;
}
}

}

}
Expand Down
61 changes: 55 additions & 6 deletions probe/docker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ type Container interface {

type container struct {
sync.RWMutex
container *docker.Container
statsConn ClientConn
latestStats *docker.Stats
container *docker.Container
statsConn ClientConn
latestStats *docker.Stats
pendingStats []*docker.Stats
}

// NewContainer creates a new Container
Expand Down Expand Up @@ -190,6 +191,7 @@ func (c *container) StartGatheringStats() error {

c.Lock()
c.latestStats = stats
c.pendingStats = append(c.pendingStats, stats)
c.Unlock()

stats = &docker.Stats{}
Expand Down Expand Up @@ -238,6 +240,52 @@ func (c *container) ports(localAddrs []net.IP) report.StringSet {
return report.MakeStringSet(ports...)
}

func (c *container) memoryUsageMetric() report.Metric {
result := report.MakeMetric()
for _, s := range c.pendingStats {
result = result.Add(s.Read, float64(s.MemoryStats.Usage))
}
return result
}

func (c *container) cpuPercentMetric() report.Metric {
result := report.MakeMetric()
if len(c.pendingStats) < 2 {

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

return result
}

previous := c.pendingStats[0]
for _, s := range c.pendingStats[1:] {
// Copies from docker/api/client/stats.go#L205
cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage - previous.CPUStats.CPUUsage.TotalUsage)
systemDelta := float64(s.CPUStats.SystemCPUUsage - previous.CPUStats.SystemCPUUsage)
cpuPercent := 0.0
if systemDelta > 0.0 && cpuDelta > 0.0 {
cpuPercent = (cpuDelta / systemDelta) * float64(len(s.CPUStats.CPUUsage.PercpuUsage)) * 100.0
}
result = result.Add(s.Read, cpuPercent)
available := float64(len(s.CPUStats.CPUUsage.PercpuUsage)) * 100.0
if available >= result.Max {
result.Max = available
}
previous = s
}
return result
}

func (c *container) metrics() report.Metrics {
result := report.Metrics{
MemoryUsage: c.memoryUsageMetric(),
CPUTotalUsage: c.cpuPercentMetric(),
}

// Keep the latest report to help with relative metric reporting.
if len(c.pendingStats) > 0 {
c.pendingStats = c.pendingStats[len(c.pendingStats)-1:]
}
return result
}

func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
c.RLock()
defer c.RUnlock()
Expand Down Expand Up @@ -269,7 +317,9 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
ContainerPorts: c.ports(localAddrs),
ContainerIPs: report.MakeStringSet(ips...),
ContainerIPsWithScopes: report.MakeStringSet(ipsWithScopes...),
}).WithLatest(ContainerState, mtime.Now(), state)
}).WithLatest(
ContainerState, mtime.Now(), state,
).WithMetrics(c.metrics())

if c.container.State.Paused {
result = result.WithControls(UnpauseContainer)
Expand Down Expand Up @@ -305,8 +355,7 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
CPUTotalUsage: strconv.FormatUint(c.latestStats.CPUStats.CPUUsage.TotalUsage, 10),
CPUUsageInKernelmode: strconv.FormatUint(c.latestStats.CPUStats.CPUUsage.UsageInKernelmode, 10),
CPUSystemCPUUsage: strconv.FormatUint(c.latestStats.CPUStats.SystemCPUUsage, 10),
})

}).WithMetrics(c.metrics())
return result
}

Expand Down
17 changes: 11 additions & 6 deletions probe/docker/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,18 @@ func TestContainer(t *testing.T) {
}
defer c.StopGatheringStats()

now := time.Unix(12345, 67890).UTC()
mtime.NowForce(now)
defer mtime.NowReset()

// Send some stats to the docker container
stats := &client.Stats{}
stats.Read = now
stats.MemoryStats.Usage = 12345
if err = json.NewEncoder(writer).Encode(&stats); err != nil {
t.Error(err)
}

now := time.Now()
mtime.NowForce(now)
defer mtime.NowReset()

// Now see if we go them
want := report.MakeNode().WithMetadata(map[string]string{
"docker_container_command": " ",
Expand All @@ -85,8 +86,12 @@ func TestContainer(t *testing.T) {
"docker_container_ips_with_scopes": report.MakeStringSet("scope;1.2.3.4"),
}).WithControls(
docker.RestartContainer, docker.StopContainer, docker.PauseContainer,
).WithLatest("docker_container_state", now, "running")

).WithLatest(
"docker_container_state", now, "running",
).WithMetrics(report.Metrics{
"cpu_total_usage": report.MakeMetric(),
"memory_usage": report.MakeMetric().Add(now, 12345),
})
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
node := c.GetNode("scope", []net.IP{})
for k, v := range node.Metadata {
Expand Down
7 changes: 4 additions & 3 deletions probe/host/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ const (
HostName = "host_name"
LocalNetworks = "local_networks"
OS = "os"
Load = "load"
KernelVersion = "kernel_version"
Uptime = "uptime"
Load1 = "load1"
Load5 = "load5"
Load15 = "load15"
)

// Exposed for testing.
Expand Down Expand Up @@ -71,12 +73,11 @@ func (r *Reporter) Report() (report.Report, error) {
Timestamp: Now(),
HostName: r.hostName,
OS: runtime.GOOS,
Load: GetLoad(),
KernelVersion: kernel,
Uptime: uptime.String(),
}).WithSets(report.Sets{
LocalNetworks: report.MakeStringSet(localCIDRs...),
}))
}).WithMetrics(GetLoad()))

return rep, nil
}
Loading