github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/ui/tests/unit/utils/node-stats-tracker-test.js (about) 1 import EmberObject from '@ember/object'; 2 import { assign } from '@ember/polyfills'; 3 import { module, test } from 'qunit'; 4 import sinon from 'sinon'; 5 import Pretender from 'pretender'; 6 import NodeStatsTracker, { stats } from 'nomad-ui/utils/classes/node-stats-tracker'; 7 import fetch from 'nomad-ui/utils/fetch'; 8 import statsTrackerFrameMissingBehavior from './behaviors/stats-tracker-frame-missing'; 9 10 import { settled } from '@ember/test-helpers'; 11 12 module('Unit | Util | NodeStatsTracker', function() { 13 const refDate = Date.now() * 1000000; 14 const makeDate = ts => new Date(ts / 1000000); 15 16 const MockNode = overrides => 17 assign( 18 { 19 id: 'some-identifier', 20 resources: { 21 cpu: 2000, 22 memory: 4096, 23 }, 24 }, 25 overrides 26 ); 27 28 const mockFrame = step => ({ 29 CPUTicksConsumed: step + 1000, 30 Memory: { 31 Used: (step + 2048) * 1024 * 1024, 32 }, 33 Timestamp: refDate + step, 34 }); 35 36 test('the NodeStatsTracker constructor expects a fetch definition and a node', async function(assert) { 37 const tracker = NodeStatsTracker.create(); 38 assert.throws( 39 () => { 40 tracker.fetch(); 41 }, 42 /StatsTrackers need a fetch method/, 43 'Polling does not work without a fetch method provided' 44 ); 45 }); 46 47 test('the url property is computed based off the node id', async function(assert) { 48 const node = MockNode(); 49 const tracker = NodeStatsTracker.create({ fetch, node }); 50 51 assert.equal( 52 tracker.get('url'), 53 `/v1/client/stats?node_id=${node.id}`, 54 'Url is derived from the node id' 55 ); 56 }); 57 58 test('reservedCPU and reservedMemory properties come from the node', async function(assert) { 59 const node = MockNode(); 60 const tracker = NodeStatsTracker.create({ fetch, node }); 61 62 assert.equal(tracker.get('reservedCPU'), node.resources.cpu, 'reservedCPU comes from the node'); 63 assert.equal( 64 tracker.get('reservedMemory'), 65 node.resources.memory, 66 'reservedMemory comes from the node' 67 ); 68 }); 69 70 test('poll results in requesting the url and calling append with the resulting JSON', async function(assert) { 71 const node = MockNode(); 72 const tracker = NodeStatsTracker.create({ fetch, node, append: sinon.spy() }); 73 const mockFrame = { 74 Some: { 75 data: ['goes', 'here'], 76 twelve: 12, 77 }, 78 }; 79 80 const server = new Pretender(function() { 81 this.get('/v1/client/stats', () => [200, {}, JSON.stringify(mockFrame)]); 82 }); 83 84 tracker.get('poll').perform(); 85 86 assert.equal(server.handledRequests.length, 1, 'Only one request was made'); 87 assert.equal( 88 server.handledRequests[0].url, 89 `/v1/client/stats?node_id=${node.id}`, 90 'The correct URL was requested' 91 ); 92 93 await settled(); 94 assert.ok( 95 tracker.append.calledWith(mockFrame), 96 'The JSON response was passed into append as a POJO' 97 ); 98 99 server.shutdown(); 100 }); 101 102 test('append appropriately maps a data frame to the tracked stats for cpu and memory for the node', async function(assert) { 103 const node = MockNode(); 104 const tracker = NodeStatsTracker.create({ fetch, node }); 105 106 assert.deepEqual(tracker.get('cpu'), [], 'No tracked cpu yet'); 107 assert.deepEqual(tracker.get('memory'), [], 'No tracked memory yet'); 108 109 tracker.append(mockFrame(1)); 110 111 assert.deepEqual( 112 tracker.get('cpu'), 113 [{ timestamp: makeDate(refDate + 1), used: 1001, percent: 1001 / 2000 }], 114 'One frame of cpu' 115 ); 116 117 assert.deepEqual( 118 tracker.get('memory'), 119 [{ timestamp: makeDate(refDate + 1), used: 2049 * 1024 * 1024, percent: 2049 / 4096 }], 120 'One frame of memory' 121 ); 122 123 tracker.append(mockFrame(2)); 124 125 assert.deepEqual( 126 tracker.get('cpu'), 127 [ 128 { timestamp: makeDate(refDate + 1), used: 1001, percent: 1001 / 2000 }, 129 { timestamp: makeDate(refDate + 2), used: 1002, percent: 1002 / 2000 }, 130 ], 131 'Two frames of cpu' 132 ); 133 134 assert.deepEqual( 135 tracker.get('memory'), 136 [ 137 { timestamp: makeDate(refDate + 1), used: 2049 * 1024 * 1024, percent: 2049 / 4096 }, 138 { timestamp: makeDate(refDate + 2), used: 2050 * 1024 * 1024, percent: 2050 / 4096 }, 139 ], 140 'Two frames of memory' 141 ); 142 }); 143 144 test('each stat list has maxLength equal to bufferSize', async function(assert) { 145 const node = MockNode(); 146 const bufferSize = 10; 147 const tracker = NodeStatsTracker.create({ fetch, node, bufferSize }); 148 149 for (let i = 1; i <= 20; i++) { 150 tracker.append(mockFrame(i)); 151 } 152 153 assert.equal( 154 tracker.get('cpu.length'), 155 bufferSize, 156 `20 calls to append, only ${bufferSize} frames in the stats array` 157 ); 158 assert.equal( 159 tracker.get('memory.length'), 160 bufferSize, 161 `20 calls to append, only ${bufferSize} frames in the stats array` 162 ); 163 164 assert.equal( 165 +tracker.get('cpu')[0].timestamp, 166 +makeDate(refDate + 11), 167 'Old frames are removed in favor of newer ones' 168 ); 169 assert.equal( 170 +tracker.get('memory')[0].timestamp, 171 +makeDate(refDate + 11), 172 'Old frames are removed in favor of newer ones' 173 ); 174 }); 175 176 test('the stats computed property macro constructs a NodeStatsTracker based on a nodeProp and a fetch definition', async function(assert) { 177 const node = MockNode(); 178 const fetchSpy = sinon.spy(); 179 180 const SomeClass = EmberObject.extend({ 181 stats: stats('theNode', function() { 182 return () => fetchSpy(this); 183 }), 184 }); 185 const someObject = SomeClass.create({ 186 theNode: node, 187 }); 188 189 assert.equal( 190 someObject.get('stats.url'), 191 `/v1/client/stats?node_id=${node.id}`, 192 'stats computed property macro creates a NodeStatsTracker' 193 ); 194 195 someObject.get('stats').fetch(); 196 197 assert.ok( 198 fetchSpy.calledWith(someObject), 199 'the fetch factory passed into the macro gets called to assign a bound version of fetch to the NodeStatsTracker instance' 200 ); 201 }); 202 203 test('changing the value of the nodeProp constructs a new NodeStatsTracker', async function(assert) { 204 const node1 = MockNode(); 205 const node2 = MockNode(); 206 const SomeClass = EmberObject.extend({ 207 stats: stats('theNode', () => fetch), 208 }); 209 210 const someObject = SomeClass.create({ 211 theNode: node1, 212 }); 213 214 const stats1 = someObject.get('stats'); 215 216 someObject.set('theNode', node2); 217 const stats2 = someObject.get('stats'); 218 219 assert.notOk( 220 stats1 === stats2, 221 'Changing the value of the node results in creating a new NodeStatsTracker instance' 222 ); 223 }); 224 225 statsTrackerFrameMissingBehavior({ 226 resourceName: 'node', 227 ResourceConstructor: MockNode, 228 TrackerConstructor: NodeStatsTracker, 229 mockFrame, 230 compileResources(frame) { 231 const timestamp = makeDate(frame.Timestamp); 232 return [ 233 { 234 timestamp, 235 used: frame.CPUTicksConsumed, 236 percent: frame.CPUTicksConsumed / 2000, 237 }, 238 { 239 timestamp, 240 used: frame.Memory.Used, 241 percent: frame.Memory.Used / 1024 / 1024 / 4096, 242 }, 243 ]; 244 }, 245 }); 246 });