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