-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4635 from hashicorp/f-ui-stat-trackers
UI: Stats trackers
- Loading branch information
Showing
13 changed files
with
1,038 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import Route from '@ember/routing/route'; | ||
|
||
export default Route.extend({ | ||
setupController(controller) { | ||
this._super(...arguments); | ||
controller.get('pollStats').perform(); | ||
}, | ||
|
||
resetController(controller) { | ||
controller.get('pollStats').cancelAll(); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import Mixin from '@ember/object/mixin'; | ||
import { assert } from '@ember/debug'; | ||
|
||
export default Mixin.create({ | ||
url: '', | ||
|
||
fetch() { | ||
assert('StatsTrackers need a fetch method, which should have an interface like window.fetch'); | ||
}, | ||
|
||
append(/* frame */) { | ||
assert( | ||
'StatsTrackers need an append method, which takes the JSON response from a request to url as an argument' | ||
); | ||
}, | ||
|
||
poll() { | ||
const url = this.get('url'); | ||
assert('Url must be defined', url); | ||
|
||
return this.get('fetch')(url) | ||
.then(res => { | ||
return res.json(); | ||
}) | ||
.then(frame => this.append(frame)); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import EmberObject, { computed, get } from '@ember/object'; | ||
import { alias } from '@ember/object/computed'; | ||
import RollingArray from 'nomad-ui/utils/classes/rolling-array'; | ||
import AbstractStatsTracker from 'nomad-ui/utils/classes/abstract-stats-tracker'; | ||
|
||
const percent = (numerator, denominator) => { | ||
if (!numerator || !denominator) { | ||
return 0; | ||
} | ||
return numerator / denominator; | ||
}; | ||
|
||
const AllocationStatsTracker = EmberObject.extend(AbstractStatsTracker, { | ||
// Set via the stats computed property macro | ||
allocation: null, | ||
|
||
bufferSize: 100, | ||
|
||
url: computed('allocation', function() { | ||
return `/v1/client/allocation/${this.get('allocation.id')}/stats`; | ||
}), | ||
|
||
append(frame) { | ||
const cpuUsed = Math.floor(frame.ResourceUsage.CpuStats.TotalTicks) || 0; | ||
this.get('cpu').push({ | ||
timestamp: frame.Timestamp, | ||
used: cpuUsed, | ||
percent: percent(cpuUsed, this.get('reservedCPU')), | ||
}); | ||
|
||
const memoryUsed = frame.ResourceUsage.MemoryStats.RSS; | ||
this.get('memory').push({ | ||
timestamp: frame.Timestamp, | ||
used: memoryUsed, | ||
percent: percent(memoryUsed / 1024 / 1024, this.get('reservedMemory')), | ||
}); | ||
|
||
for (var taskName in frame.Tasks) { | ||
const taskFrame = frame.Tasks[taskName]; | ||
const stats = this.get('tasks').findBy('task', taskName); | ||
|
||
// If for whatever reason there is a task in the frame data that isn't in the | ||
// allocation, don't attempt to append data for the task. | ||
if (!stats) continue; | ||
|
||
const taskCpuUsed = Math.floor(taskFrame.ResourceUsage.CpuStats.TotalTicks) || 0; | ||
stats.cpu.push({ | ||
timestamp: taskFrame.Timestamp, | ||
used: taskCpuUsed, | ||
percent: percent(taskCpuUsed, stats.reservedCPU), | ||
}); | ||
|
||
const taskMemoryUsed = taskFrame.ResourceUsage.MemoryStats.RSS; | ||
stats.memory.push({ | ||
timestamp: taskFrame.Timestamp, | ||
used: taskMemoryUsed, | ||
percent: percent(taskMemoryUsed / 1024 / 1024, stats.reservedMemory), | ||
}); | ||
} | ||
}, | ||
|
||
// Static figures, denominators for stats | ||
reservedCPU: alias('allocation.taskGroup.reservedCPU'), | ||
reservedMemory: alias('allocation.taskGroup.reservedMemory'), | ||
|
||
// Dynamic figures, collected over time | ||
// []{ timestamp: Date, used: Number, percent: Number } | ||
cpu: computed('allocation', function() { | ||
return RollingArray(this.get('bufferSize')); | ||
}), | ||
memory: computed('allocation', function() { | ||
return RollingArray(this.get('bufferSize')); | ||
}), | ||
|
||
tasks: computed('allocation', function() { | ||
const bufferSize = this.get('bufferSize'); | ||
return this.get('allocation.taskGroup.tasks').map(task => ({ | ||
task: get(task, 'name'), | ||
|
||
// Static figures, denominators for stats | ||
reservedCPU: get(task, 'reservedCPU'), | ||
reservedMemory: get(task, 'reservedMemory'), | ||
|
||
// Dynamic figures, collected over time | ||
// []{ timestamp: Date, used: Number, percent: Number } | ||
cpu: RollingArray(bufferSize), | ||
memory: RollingArray(bufferSize), | ||
})); | ||
}), | ||
}); | ||
|
||
export default AllocationStatsTracker; | ||
|
||
export function stats(allocationProp, fetch) { | ||
return computed(allocationProp, function() { | ||
return AllocationStatsTracker.create({ | ||
fetch: fetch.call(this), | ||
allocation: this.get(allocationProp), | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import EmberObject, { computed } from '@ember/object'; | ||
import { alias } from '@ember/object/computed'; | ||
import RollingArray from 'nomad-ui/utils/classes/rolling-array'; | ||
import AbstractStatsTracker from 'nomad-ui/utils/classes/abstract-stats-tracker'; | ||
|
||
const percent = (numerator, denominator) => { | ||
if (!numerator || !denominator) { | ||
return 0; | ||
} | ||
return numerator / denominator; | ||
}; | ||
|
||
const NodeStatsTracker = EmberObject.extend(AbstractStatsTracker, { | ||
// Set via the stats computed property macro | ||
node: null, | ||
|
||
bufferSize: 100, | ||
|
||
url: computed('node', function() { | ||
return `/v1/client/stats?node_id=${this.get('node.id')}`; | ||
}), | ||
|
||
append(frame) { | ||
const cpuUsed = Math.floor(frame.CPUTicksConsumed) || 0; | ||
this.get('cpu').push({ | ||
timestamp: frame.Timestamp, | ||
used: cpuUsed, | ||
percent: percent(cpuUsed, this.get('reservedCPU')), | ||
}); | ||
|
||
const memoryUsed = frame.Memory.Used; | ||
this.get('memory').push({ | ||
timestamp: frame.Timestamp, | ||
used: memoryUsed, | ||
percent: percent(memoryUsed / 1024 / 1024, this.get('reservedMemory')), | ||
}); | ||
}, | ||
|
||
// Static figures, denominators for stats | ||
reservedCPU: alias('node.resources.cpu'), | ||
reservedMemory: alias('node.resources.memory'), | ||
|
||
// Dynamic figures, collected over time | ||
// []{ timestamp: Date, used: Number, percent: Number } | ||
cpu: computed('node', function() { | ||
return RollingArray(this.get('bufferSize')); | ||
}), | ||
memory: computed('node', function() { | ||
return RollingArray(this.get('bufferSize')); | ||
}), | ||
}); | ||
|
||
export default NodeStatsTracker; | ||
|
||
export function stats(nodeProp, fetch) { | ||
return computed(nodeProp, function() { | ||
return NodeStatsTracker.create({ | ||
fetch: fetch.call(this), | ||
node: this.get(nodeProp), | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// An array with a max length. | ||
// | ||
// When max length is surpassed, items are removed from | ||
// the front of the array. | ||
|
||
// Using Classes to extend Array is unsupported in Babel so this less | ||
// ideal approach is taken: https://babeljs.io/docs/en/caveats#classes | ||
export default function RollingArray(maxLength, ...items) { | ||
const array = new Array(...items); | ||
array.maxLength = maxLength; | ||
|
||
// Capture the originals of each array method, but | ||
// associate them with the array to prevent closures. | ||
array._push = array.push; | ||
array._splice = array.splice; | ||
array._unshift = array.unshift; | ||
|
||
array.push = function(...items) { | ||
const returnValue = this._push(...items); | ||
|
||
const surplus = this.length - this.maxLength; | ||
if (surplus > 0) { | ||
this.splice(0, surplus); | ||
} | ||
|
||
return Math.min(returnValue, this.maxLength); | ||
}; | ||
|
||
array.splice = function(...args) { | ||
const returnValue = this._splice(...args); | ||
|
||
const surplus = this.length - this.maxLength; | ||
if (surplus > 0) { | ||
this._splice(0, surplus); | ||
} | ||
|
||
return returnValue; | ||
}; | ||
|
||
array.unshift = function() { | ||
throw new Error('Cannot unshift onto a RollingArray'); | ||
}; | ||
|
||
return array; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.