github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/ui/tests/unit/utils/allocation-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 AllocationStatsTracker, { stats } from 'nomad-ui/utils/classes/allocation-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 | AllocationStatsTracker', function() { 13 const refDate = Date.now() * 1000000; 14 const makeDate = ts => new Date(ts / 1000000); 15 16 const MockAllocation = overrides => 17 assign( 18 { 19 id: 'some-identifier', 20 taskGroup: { 21 reservedCPU: 200, 22 reservedMemory: 512, 23 tasks: [ 24 { 25 name: 'service', 26 reservedCPU: 100, 27 reservedMemory: 256, 28 }, 29 { 30 name: 'log-shipper', 31 reservedCPU: 50, 32 reservedMemory: 128, 33 }, 34 { 35 name: 'sidecar', 36 reservedCPU: 50, 37 reservedMemory: 128, 38 }, 39 ], 40 }, 41 }, 42 overrides 43 ); 44 45 const mockFrame = step => ({ 46 ResourceUsage: { 47 CpuStats: { 48 TotalTicks: step + 100, 49 }, 50 MemoryStats: { 51 RSS: (step + 400) * 1024 * 1024, 52 }, 53 }, 54 Tasks: { 55 service: { 56 ResourceUsage: { 57 CpuStats: { 58 TotalTicks: step + 50, 59 }, 60 MemoryStats: { 61 RSS: (step + 100) * 1024 * 1024, 62 }, 63 }, 64 Timestamp: refDate + step, 65 }, 66 'log-shipper': { 67 ResourceUsage: { 68 CpuStats: { 69 TotalTicks: step + 25, 70 }, 71 MemoryStats: { 72 RSS: (step + 50) * 1024 * 1024, 73 }, 74 }, 75 Timestamp: refDate + step * 10, 76 }, 77 sidecar: { 78 ResourceUsage: { 79 CpuStats: { 80 TotalTicks: step + 26, 81 }, 82 MemoryStats: { 83 RSS: (step + 51) * 1024 * 1024, 84 }, 85 }, 86 Timestamp: refDate + step * 100, 87 }, 88 }, 89 Timestamp: refDate + step * 1000, 90 }); 91 92 test('the AllocationStatsTracker constructor expects a fetch definition and an allocation', async function(assert) { 93 const tracker = AllocationStatsTracker.create(); 94 assert.throws( 95 () => { 96 tracker.fetch(); 97 }, 98 /StatsTrackers need a fetch method/, 99 'Polling does not work without a fetch method provided' 100 ); 101 }); 102 103 test('the url property is computed based off the allocation id', async function(assert) { 104 const allocation = MockAllocation(); 105 const tracker = AllocationStatsTracker.create({ fetch, allocation }); 106 107 assert.equal( 108 tracker.get('url'), 109 `/v1/client/allocation/${allocation.id}/stats`, 110 'Url is derived from the allocation id' 111 ); 112 }); 113 114 test('reservedCPU and reservedMemory properties come from the allocation', async function(assert) { 115 const allocation = MockAllocation(); 116 const tracker = AllocationStatsTracker.create({ fetch, allocation }); 117 118 assert.equal( 119 tracker.get('reservedCPU'), 120 allocation.taskGroup.reservedCPU, 121 'reservedCPU comes from the allocation task group' 122 ); 123 assert.equal( 124 tracker.get('reservedMemory'), 125 allocation.taskGroup.reservedMemory, 126 'reservedMemory comes from the allocation task group' 127 ); 128 }); 129 130 test('the tasks list comes from the allocation', async function(assert) { 131 const allocation = MockAllocation(); 132 const tracker = AllocationStatsTracker.create({ fetch, allocation }); 133 134 assert.equal( 135 tracker.get('tasks.length'), 136 allocation.taskGroup.tasks.length, 137 'tasks matches lengths with the allocation task group' 138 ); 139 allocation.taskGroup.tasks.forEach(task => { 140 const trackerTask = tracker.get('tasks').findBy('task', task.name); 141 assert.equal(trackerTask.reservedCPU, task.reservedCPU, `CPU matches for task ${task.name}`); 142 assert.equal( 143 trackerTask.reservedMemory, 144 task.reservedMemory, 145 `Memory matches for task ${task.name}` 146 ); 147 }); 148 }); 149 150 test('poll results in requesting the url and calling append with the resulting JSON', async function(assert) { 151 const allocation = MockAllocation(); 152 const tracker = AllocationStatsTracker.create({ fetch, allocation, append: sinon.spy() }); 153 const mockFrame = { 154 Some: { 155 data: ['goes', 'here'], 156 twelve: 12, 157 }, 158 }; 159 160 const server = new Pretender(function() { 161 this.get('/v1/client/allocation/:id/stats', () => [200, {}, JSON.stringify(mockFrame)]); 162 }); 163 164 tracker.get('poll').perform(); 165 166 assert.equal(server.handledRequests.length, 1, 'Only one request was made'); 167 assert.equal( 168 server.handledRequests[0].url, 169 `/v1/client/allocation/${allocation.id}/stats`, 170 'The correct URL was requested' 171 ); 172 173 await settled(); 174 assert.ok( 175 tracker.append.calledWith(mockFrame), 176 'The JSON response was passed onto append as a POJO' 177 ); 178 179 server.shutdown(); 180 }); 181 182 test('append appropriately maps a data frame to the tracked stats for cpu and memory for the allocation as well as individual tasks', async function(assert) { 183 const allocation = MockAllocation(); 184 const tracker = AllocationStatsTracker.create({ fetch, allocation }); 185 186 assert.deepEqual(tracker.get('cpu'), [], 'No tracked cpu yet'); 187 assert.deepEqual(tracker.get('memory'), [], 'No tracked memory yet'); 188 189 assert.deepEqual( 190 tracker.get('tasks'), 191 [ 192 { task: 'service', reservedCPU: 100, reservedMemory: 256, cpu: [], memory: [] }, 193 { task: 'log-shipper', reservedCPU: 50, reservedMemory: 128, cpu: [], memory: [] }, 194 { task: 'sidecar', reservedCPU: 50, reservedMemory: 128, cpu: [], memory: [] }, 195 ], 196 'tasks represents the tasks for the allocation with no stats yet' 197 ); 198 199 tracker.append(mockFrame(1)); 200 201 assert.deepEqual( 202 tracker.get('cpu'), 203 [{ timestamp: makeDate(refDate + 1000), used: 101, percent: 101 / 200 }], 204 'One frame of cpu' 205 ); 206 assert.deepEqual( 207 tracker.get('memory'), 208 [ 209 { 210 timestamp: makeDate(refDate + 1000), 211 used: 401 * 1024 * 1024, 212 percent: 401 / 512, 213 }, 214 ], 215 'One frame of memory' 216 ); 217 218 assert.deepEqual( 219 tracker.get('tasks'), 220 [ 221 { 222 task: 'service', 223 reservedCPU: 100, 224 reservedMemory: 256, 225 cpu: [{ timestamp: makeDate(refDate + 1), used: 51, percent: 51 / 100 }], 226 memory: [ 227 { 228 timestamp: makeDate(refDate + 1), 229 used: 101 * 1024 * 1024, 230 percent: 101 / 256, 231 }, 232 ], 233 }, 234 { 235 task: 'log-shipper', 236 reservedCPU: 50, 237 reservedMemory: 128, 238 cpu: [{ timestamp: makeDate(refDate + 10), used: 26, percent: 26 / 50 }], 239 memory: [ 240 { 241 timestamp: makeDate(refDate + 10), 242 used: 51 * 1024 * 1024, 243 percent: 51 / 128, 244 }, 245 ], 246 }, 247 { 248 task: 'sidecar', 249 reservedCPU: 50, 250 reservedMemory: 128, 251 cpu: [{ timestamp: makeDate(refDate + 100), used: 27, percent: 27 / 50 }], 252 memory: [ 253 { 254 timestamp: makeDate(refDate + 100), 255 used: 52 * 1024 * 1024, 256 percent: 52 / 128, 257 }, 258 ], 259 }, 260 ], 261 'tasks represents the tasks for the allocation, each with one frame of stats' 262 ); 263 264 tracker.append(mockFrame(2)); 265 266 assert.deepEqual( 267 tracker.get('cpu'), 268 [ 269 { timestamp: makeDate(refDate + 1000), used: 101, percent: 101 / 200 }, 270 { timestamp: makeDate(refDate + 2000), used: 102, percent: 102 / 200 }, 271 ], 272 'Two frames of cpu' 273 ); 274 assert.deepEqual( 275 tracker.get('memory'), 276 [ 277 { timestamp: makeDate(refDate + 1000), used: 401 * 1024 * 1024, percent: 401 / 512 }, 278 { timestamp: makeDate(refDate + 2000), used: 402 * 1024 * 1024, percent: 402 / 512 }, 279 ], 280 'Two frames of memory' 281 ); 282 283 assert.deepEqual( 284 tracker.get('tasks'), 285 [ 286 { 287 task: 'service', 288 reservedCPU: 100, 289 reservedMemory: 256, 290 cpu: [ 291 { timestamp: makeDate(refDate + 1), used: 51, percent: 51 / 100 }, 292 { timestamp: makeDate(refDate + 2), used: 52, percent: 52 / 100 }, 293 ], 294 memory: [ 295 { timestamp: makeDate(refDate + 1), used: 101 * 1024 * 1024, percent: 101 / 256 }, 296 { timestamp: makeDate(refDate + 2), used: 102 * 1024 * 1024, percent: 102 / 256 }, 297 ], 298 }, 299 { 300 task: 'log-shipper', 301 reservedCPU: 50, 302 reservedMemory: 128, 303 cpu: [ 304 { timestamp: makeDate(refDate + 10), used: 26, percent: 26 / 50 }, 305 { timestamp: makeDate(refDate + 20), used: 27, percent: 27 / 50 }, 306 ], 307 memory: [ 308 { timestamp: makeDate(refDate + 10), used: 51 * 1024 * 1024, percent: 51 / 128 }, 309 { timestamp: makeDate(refDate + 20), used: 52 * 1024 * 1024, percent: 52 / 128 }, 310 ], 311 }, 312 { 313 task: 'sidecar', 314 reservedCPU: 50, 315 reservedMemory: 128, 316 cpu: [ 317 { timestamp: makeDate(refDate + 100), used: 27, percent: 27 / 50 }, 318 { timestamp: makeDate(refDate + 200), used: 28, percent: 28 / 50 }, 319 ], 320 memory: [ 321 { timestamp: makeDate(refDate + 100), used: 52 * 1024 * 1024, percent: 52 / 128 }, 322 { timestamp: makeDate(refDate + 200), used: 53 * 1024 * 1024, percent: 53 / 128 }, 323 ], 324 }, 325 ], 326 'tasks represents the tasks for the allocation, each with two frames of stats' 327 ); 328 }); 329 330 test('each stat list has maxLength equal to bufferSize', async function(assert) { 331 const allocation = MockAllocation(); 332 const bufferSize = 10; 333 const tracker = AllocationStatsTracker.create({ fetch, allocation, bufferSize }); 334 335 for (let i = 1; i <= 20; i++) { 336 tracker.append(mockFrame(i)); 337 } 338 339 assert.equal( 340 tracker.get('cpu.length'), 341 bufferSize, 342 `20 calls to append, only ${bufferSize} frames in the stats array` 343 ); 344 assert.equal( 345 tracker.get('memory.length'), 346 bufferSize, 347 `20 calls to append, only ${bufferSize} frames in the stats array` 348 ); 349 350 assert.equal( 351 +tracker.get('cpu')[0].timestamp, 352 +makeDate(refDate + 11000), 353 'Old frames are removed in favor of newer ones' 354 ); 355 assert.equal( 356 +tracker.get('memory')[0].timestamp, 357 +makeDate(refDate + 11000), 358 'Old frames are removed in favor of newer ones' 359 ); 360 361 tracker.get('tasks').forEach(task => { 362 assert.equal( 363 task.cpu.length, 364 bufferSize, 365 `20 calls to append, only ${bufferSize} frames in the stats array` 366 ); 367 assert.equal( 368 task.memory.length, 369 bufferSize, 370 `20 calls to append, only ${bufferSize} frames in the stats array` 371 ); 372 }); 373 374 assert.equal( 375 +tracker.get('tasks').findBy('task', 'service').cpu[0].timestamp, 376 +makeDate(refDate + 11), 377 'Old frames are removed in favor of newer ones' 378 ); 379 assert.equal( 380 +tracker.get('tasks').findBy('task', 'service').memory[0].timestamp, 381 +makeDate(refDate + 11), 382 'Old frames are removed in favor of newer ones' 383 ); 384 385 assert.equal( 386 +tracker.get('tasks').findBy('task', 'log-shipper').cpu[0].timestamp, 387 +makeDate(refDate + 110), 388 'Old frames are removed in favor of newer ones' 389 ); 390 assert.equal( 391 +tracker.get('tasks').findBy('task', 'log-shipper').memory[0].timestamp, 392 +makeDate(refDate + 110), 393 'Old frames are removed in favor of newer ones' 394 ); 395 396 assert.equal( 397 +tracker.get('tasks').findBy('task', 'sidecar').cpu[0].timestamp, 398 +makeDate(refDate + 1100), 399 'Old frames are removed in favor of newer ones' 400 ); 401 assert.equal( 402 +tracker.get('tasks').findBy('task', 'sidecar').memory[0].timestamp, 403 +makeDate(refDate + 1100), 404 'Old frames are removed in favor of newer ones' 405 ); 406 }); 407 408 test('the stats computed property macro constructs an AllocationStatsTracker based on an allocationProp and a fetch definition', async function(assert) { 409 const allocation = MockAllocation(); 410 const fetchSpy = sinon.spy(); 411 412 const SomeClass = EmberObject.extend({ 413 stats: stats('alloc', function() { 414 return () => fetchSpy(this); 415 }), 416 }); 417 const someObject = SomeClass.create({ 418 alloc: allocation, 419 }); 420 421 assert.equal( 422 someObject.get('stats.url'), 423 `/v1/client/allocation/${allocation.id}/stats`, 424 'stats computed property macro creates an AllocationStatsTracker' 425 ); 426 427 someObject.get('stats').fetch(); 428 429 assert.ok( 430 fetchSpy.calledWith(someObject), 431 'the fetch factory passed into the macro gets called to assign a bound version of fetch to the AllocationStatsTracker instance' 432 ); 433 }); 434 435 test('changing the value of the allocationProp constructs a new AllocationStatsTracker', async function(assert) { 436 const alloc1 = MockAllocation(); 437 const alloc2 = MockAllocation(); 438 const SomeClass = EmberObject.extend({ 439 stats: stats('alloc', () => fetch), 440 }); 441 442 const someObject = SomeClass.create({ 443 alloc: alloc1, 444 }); 445 446 const stats1 = someObject.get('stats'); 447 448 someObject.set('alloc', alloc2); 449 const stats2 = someObject.get('stats'); 450 451 assert.notOk( 452 stats1 === stats2, 453 'Changing the value of alloc results in creating a new AllocationStatsTracker instance' 454 ); 455 }); 456 457 statsTrackerFrameMissingBehavior({ 458 resourceName: 'allocation', 459 ResourceConstructor: MockAllocation, 460 TrackerConstructor: AllocationStatsTracker, 461 mockFrame, 462 compileResources(frame) { 463 const timestamp = makeDate(frame.Timestamp); 464 const cpu = frame.ResourceUsage.CpuStats.TotalTicks; 465 const memory = frame.ResourceUsage.MemoryStats.RSS; 466 return [ 467 { 468 timestamp, 469 used: cpu, 470 percent: cpu / 200, 471 }, 472 { 473 timestamp, 474 used: memory, 475 percent: memory / 1024 / 1024 / 512, 476 }, 477 ]; 478 }, 479 }); 480 });