github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/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, {
     7    stats,
     8  } from 'nomad-ui/utils/classes/allocation-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 | AllocationStatsTracker', function () {
    15    const refDate = Date.now() * 1000000;
    16    const makeDate = (ts) => new Date(ts / 1000000);
    17  
    18    const MockAllocation = (overrides) =>
    19      assign(
    20        {
    21          id: 'some-identifier',
    22          taskGroup: {
    23            reservedCPU: 200,
    24            reservedMemory: 512,
    25            tasks: [
    26              {
    27                name: 'log-shipper',
    28                reservedCPU: 50,
    29                reservedMemory: 128,
    30                lifecycleName: 'poststop',
    31              },
    32              {
    33                name: 'service',
    34                reservedCPU: 100,
    35                reservedMemory: 256,
    36                lifecycleName: 'main',
    37              },
    38              {
    39                name: 'sidecar',
    40                reservedCPU: 50,
    41                reservedMemory: 128,
    42                lifecycleName: 'prestart-sidecar',
    43              },
    44            ],
    45          },
    46        },
    47        overrides
    48      );
    49  
    50    const mockFrame = (step) => ({
    51      ResourceUsage: {
    52        CpuStats: {
    53          TotalTicks: step + 100,
    54        },
    55        MemoryStats: {
    56          RSS: (step + 400) * 1024 * 1024,
    57          Usage: (step + 400) * 1024 * 1024,
    58        },
    59      },
    60      Tasks: {
    61        service: {
    62          ResourceUsage: {
    63            CpuStats: {
    64              TotalTicks: step + 50,
    65            },
    66            MemoryStats: {
    67              RSS: (step + 100) * 1024 * 1024,
    68              Usage: (step + 100) * 1024 * 1024,
    69            },
    70          },
    71          Timestamp: refDate + step,
    72        },
    73        'log-shipper': {
    74          ResourceUsage: {
    75            CpuStats: {
    76              TotalTicks: step + 25,
    77            },
    78            MemoryStats: {
    79              RSS: (step + 50) * 1024 * 1024,
    80              Usage: (step + 50) * 1024 * 1024,
    81            },
    82          },
    83          Timestamp: refDate + step * 10,
    84        },
    85        sidecar: {
    86          ResourceUsage: {
    87            CpuStats: {
    88              TotalTicks: step + 26,
    89            },
    90            MemoryStats: {
    91              RSS: (step + 51) * 1024 * 1024,
    92              Usage: (step + 51) * 1024 * 1024,
    93            },
    94          },
    95          Timestamp: refDate + step * 100,
    96        },
    97      },
    98      Timestamp: refDate + step * 1000,
    99    });
   100  
   101    test('the AllocationStatsTracker constructor expects a fetch definition and an allocation', async function (assert) {
   102      const tracker = AllocationStatsTracker.create();
   103      assert.throws(
   104        () => {
   105          tracker.fetch();
   106        },
   107        /StatsTrackers need a fetch method/,
   108        'Polling does not work without a fetch method provided'
   109      );
   110    });
   111  
   112    test('the url property is computed based off the allocation id', async function (assert) {
   113      const allocation = MockAllocation();
   114      const tracker = AllocationStatsTracker.create({ fetch, allocation });
   115  
   116      assert.equal(
   117        tracker.get('url'),
   118        `/v1/client/allocation/${allocation.id}/stats`,
   119        'Url is derived from the allocation id'
   120      );
   121    });
   122  
   123    test('reservedCPU and reservedMemory properties come from the allocation', async function (assert) {
   124      const allocation = MockAllocation();
   125      const tracker = AllocationStatsTracker.create({ fetch, allocation });
   126  
   127      assert.equal(
   128        tracker.get('reservedCPU'),
   129        allocation.taskGroup.reservedCPU,
   130        'reservedCPU comes from the allocation task group'
   131      );
   132      assert.equal(
   133        tracker.get('reservedMemory'),
   134        allocation.taskGroup.reservedMemory,
   135        'reservedMemory comes from the allocation task group'
   136      );
   137    });
   138  
   139    test('the tasks list comes from the allocation', async function (assert) {
   140      assert.expect(7);
   141  
   142      const allocation = MockAllocation();
   143      const tracker = AllocationStatsTracker.create({ fetch, allocation });
   144  
   145      assert.equal(
   146        tracker.get('tasks.length'),
   147        allocation.taskGroup.tasks.length,
   148        'tasks matches lengths with the allocation task group'
   149      );
   150      allocation.taskGroup.tasks.forEach((task) => {
   151        const trackerTask = tracker.get('tasks').findBy('task', task.name);
   152        assert.equal(
   153          trackerTask.reservedCPU,
   154          task.reservedCPU,
   155          `CPU matches for task ${task.name}`
   156        );
   157        assert.equal(
   158          trackerTask.reservedMemory,
   159          task.reservedMemory,
   160          `Memory matches for task ${task.name}`
   161        );
   162      });
   163    });
   164  
   165    test('poll results in requesting the url and calling append with the resulting JSON', async function (assert) {
   166      const allocation = MockAllocation();
   167      const tracker = AllocationStatsTracker.create({
   168        fetch,
   169        allocation,
   170        append: sinon.spy(),
   171      });
   172      const mockFrame = {
   173        Some: {
   174          data: ['goes', 'here'],
   175          twelve: 12,
   176        },
   177      };
   178  
   179      const server = new Pretender(function () {
   180        this.get('/v1/client/allocation/:id/stats', () => [
   181          200,
   182          {},
   183          JSON.stringify(mockFrame),
   184        ]);
   185      });
   186  
   187      tracker.get('poll').perform();
   188  
   189      assert.equal(server.handledRequests.length, 1, 'Only one request was made');
   190      assert.equal(
   191        server.handledRequests[0].url,
   192        `/v1/client/allocation/${allocation.id}/stats`,
   193        'The correct URL was requested'
   194      );
   195  
   196      await settled();
   197      assert.ok(
   198        tracker.append.calledWith(mockFrame),
   199        'The JSON response was passed onto append as a POJO'
   200      );
   201  
   202      server.shutdown();
   203    });
   204  
   205    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) {
   206      const allocation = MockAllocation();
   207      const tracker = AllocationStatsTracker.create({ fetch, allocation });
   208  
   209      assert.deepEqual(tracker.get('cpu'), [], 'No tracked cpu yet');
   210      assert.deepEqual(tracker.get('memory'), [], 'No tracked memory yet');
   211  
   212      assert.deepEqual(
   213        tracker.get('tasks'),
   214        [
   215          {
   216            task: 'service',
   217            reservedCPU: 100,
   218            reservedMemory: 256,
   219            cpu: [],
   220            memory: [],
   221          },
   222          {
   223            task: 'sidecar',
   224            reservedCPU: 50,
   225            reservedMemory: 128,
   226            cpu: [],
   227            memory: [],
   228          },
   229          {
   230            task: 'log-shipper',
   231            reservedCPU: 50,
   232            reservedMemory: 128,
   233            cpu: [],
   234            memory: [],
   235          },
   236        ],
   237        'tasks represents the tasks for the allocation with no stats yet'
   238      );
   239  
   240      tracker.append(mockFrame(1));
   241  
   242      assert.deepEqual(
   243        tracker.get('cpu'),
   244        [{ timestamp: makeDate(refDate + 1000), used: 101, percent: 101 / 200 }],
   245        'One frame of cpu'
   246      );
   247      assert.deepEqual(
   248        tracker.get('memory'),
   249        [
   250          {
   251            timestamp: makeDate(refDate + 1000),
   252            used: 401 * 1024 * 1024,
   253            percent: 401 / 512,
   254          },
   255        ],
   256        'One frame of memory'
   257      );
   258      assert.deepEqual(
   259        tracker.get('tasks'),
   260        [
   261          {
   262            task: 'service',
   263            reservedCPU: 100,
   264            reservedMemory: 256,
   265            cpu: [
   266              {
   267                timestamp: makeDate(refDate + 1),
   268                used: 51,
   269                percent: 51 / 100,
   270                percentStack: 51 / (100 + 50 + 50),
   271                percentTotal: 51 / (100 + 50 + 50),
   272              },
   273            ],
   274            memory: [
   275              {
   276                timestamp: makeDate(refDate + 1),
   277                used: 101 * 1024 * 1024,
   278                percent: 101 / 256,
   279                percentStack: 101 / (256 + 128 + 128),
   280                percentTotal: 101 / (256 + 128 + 128),
   281              },
   282            ],
   283          },
   284          {
   285            task: 'sidecar',
   286            reservedCPU: 50,
   287            reservedMemory: 128,
   288            cpu: [
   289              {
   290                timestamp: makeDate(refDate + 100),
   291                used: 27,
   292                percent: 27 / 50,
   293                percentStack: (27 + 51) / (100 + 50 + 50),
   294                percentTotal: 27 / (100 + 50 + 50),
   295              },
   296            ],
   297            memory: [
   298              {
   299                timestamp: makeDate(refDate + 100),
   300                used: 52 * 1024 * 1024,
   301                percent: 52 / 128,
   302                percentStack: (52 + 101) / (256 + 128 + 128),
   303                percentTotal: 52 / (256 + 128 + 128),
   304              },
   305            ],
   306          },
   307          {
   308            task: 'log-shipper',
   309            reservedCPU: 50,
   310            reservedMemory: 128,
   311            cpu: [
   312              {
   313                timestamp: makeDate(refDate + 10),
   314                used: 26,
   315                percent: 26 / 50,
   316                percentStack: (26 + 27 + 51) / (100 + 50 + 50),
   317                percentTotal: 26 / (100 + 50 + 50),
   318              },
   319            ],
   320            memory: [
   321              {
   322                timestamp: makeDate(refDate + 10),
   323                used: 51 * 1024 * 1024,
   324                percent: 51 / 128,
   325                percentStack: (51 + 52 + 101) / (256 + 128 + 128),
   326                percentTotal: 51 / (256 + 128 + 128),
   327              },
   328            ],
   329          },
   330        ],
   331        'tasks represents the tasks for the allocation, each with one frame of stats'
   332      );
   333  
   334      tracker.append(mockFrame(2));
   335  
   336      assert.deepEqual(
   337        tracker.get('cpu'),
   338        [
   339          { timestamp: makeDate(refDate + 1000), used: 101, percent: 101 / 200 },
   340          { timestamp: makeDate(refDate + 2000), used: 102, percent: 102 / 200 },
   341        ],
   342        'Two frames of cpu'
   343      );
   344      assert.deepEqual(
   345        tracker.get('memory'),
   346        [
   347          {
   348            timestamp: makeDate(refDate + 1000),
   349            used: 401 * 1024 * 1024,
   350            percent: 401 / 512,
   351          },
   352          {
   353            timestamp: makeDate(refDate + 2000),
   354            used: 402 * 1024 * 1024,
   355            percent: 402 / 512,
   356          },
   357        ],
   358        'Two frames of memory'
   359      );
   360  
   361      assert.deepEqual(
   362        tracker.get('tasks'),
   363        [
   364          {
   365            task: 'service',
   366            reservedCPU: 100,
   367            reservedMemory: 256,
   368            cpu: [
   369              {
   370                timestamp: makeDate(refDate + 1),
   371                used: 51,
   372                percent: 51 / 100,
   373                percentStack: 51 / (100 + 50 + 50),
   374                percentTotal: 51 / (100 + 50 + 50),
   375              },
   376              {
   377                timestamp: makeDate(refDate + 2),
   378                used: 52,
   379                percent: 52 / 100,
   380                percentStack: 52 / (100 + 50 + 50),
   381                percentTotal: 52 / (100 + 50 + 50),
   382              },
   383            ],
   384            memory: [
   385              {
   386                timestamp: makeDate(refDate + 1),
   387                used: 101 * 1024 * 1024,
   388                percent: 101 / 256,
   389                percentStack: 101 / (256 + 128 + 128),
   390                percentTotal: 101 / (256 + 128 + 128),
   391              },
   392              {
   393                timestamp: makeDate(refDate + 2),
   394                used: 102 * 1024 * 1024,
   395                percent: 102 / 256,
   396                percentStack: 102 / (256 + 128 + 128),
   397                percentTotal: 102 / (256 + 128 + 128),
   398              },
   399            ],
   400          },
   401          {
   402            task: 'sidecar',
   403            reservedCPU: 50,
   404            reservedMemory: 128,
   405            cpu: [
   406              {
   407                timestamp: makeDate(refDate + 100),
   408                used: 27,
   409                percent: 27 / 50,
   410                percentStack: (27 + 51) / (100 + 50 + 50),
   411                percentTotal: 27 / (100 + 50 + 50),
   412              },
   413              {
   414                timestamp: makeDate(refDate + 200),
   415                used: 28,
   416                percent: 28 / 50,
   417                percentStack: (28 + 52) / (100 + 50 + 50),
   418                percentTotal: 28 / (100 + 50 + 50),
   419              },
   420            ],
   421            memory: [
   422              {
   423                timestamp: makeDate(refDate + 100),
   424                used: 52 * 1024 * 1024,
   425                percent: 52 / 128,
   426                percentStack: (52 + 101) / (256 + 128 + 128),
   427                percentTotal: 52 / (256 + 128 + 128),
   428              },
   429              {
   430                timestamp: makeDate(refDate + 200),
   431                used: 53 * 1024 * 1024,
   432                percent: 53 / 128,
   433                percentStack: (53 + 102) / (256 + 128 + 128),
   434                percentTotal: 53 / (256 + 128 + 128),
   435              },
   436            ],
   437          },
   438          {
   439            task: 'log-shipper',
   440            reservedCPU: 50,
   441            reservedMemory: 128,
   442            cpu: [
   443              {
   444                timestamp: makeDate(refDate + 10),
   445                used: 26,
   446                percent: 26 / 50,
   447                percentStack: (26 + 27 + 51) / (100 + 50 + 50),
   448                percentTotal: 26 / (100 + 50 + 50),
   449              },
   450              {
   451                timestamp: makeDate(refDate + 20),
   452                used: 27,
   453                percent: 27 / 50,
   454                percentStack: (27 + 28 + 52) / (100 + 50 + 50),
   455                percentTotal: 27 / (100 + 50 + 50),
   456              },
   457            ],
   458            memory: [
   459              {
   460                timestamp: makeDate(refDate + 10),
   461                used: 51 * 1024 * 1024,
   462                percent: 51 / 128,
   463                percentStack: (51 + 52 + 101) / (256 + 128 + 128),
   464                percentTotal: 51 / (256 + 128 + 128),
   465              },
   466              {
   467                timestamp: makeDate(refDate + 20),
   468                used: 52 * 1024 * 1024,
   469                percent: 52 / 128,
   470                percentStack: (52 + 53 + 102) / (256 + 128 + 128),
   471                percentTotal: 52 / (256 + 128 + 128),
   472              },
   473            ],
   474          },
   475        ],
   476        'tasks represents the tasks for the allocation, each with two frames of stats'
   477      );
   478    });
   479  
   480    test('each stat list has maxLength equal to bufferSize', async function (assert) {
   481      assert.expect(16);
   482  
   483      const allocation = MockAllocation();
   484      const bufferSize = 10;
   485      const tracker = AllocationStatsTracker.create({
   486        fetch,
   487        allocation,
   488        bufferSize,
   489      });
   490  
   491      for (let i = 1; i <= 20; i++) {
   492        tracker.append(mockFrame(i));
   493      }
   494  
   495      assert.equal(
   496        tracker.get('cpu.length'),
   497        bufferSize,
   498        `20 calls to append, only ${bufferSize} frames in the stats array`
   499      );
   500      assert.equal(
   501        tracker.get('memory.length'),
   502        bufferSize,
   503        `20 calls to append, only ${bufferSize} frames in the stats array`
   504      );
   505  
   506      assert.equal(
   507        +tracker.get('cpu')[0].timestamp,
   508        +makeDate(refDate + 11000),
   509        'Old frames are removed in favor of newer ones'
   510      );
   511      assert.equal(
   512        +tracker.get('memory')[0].timestamp,
   513        +makeDate(refDate + 11000),
   514        'Old frames are removed in favor of newer ones'
   515      );
   516  
   517      tracker.get('tasks').forEach((task) => {
   518        assert.equal(
   519          task.cpu.length,
   520          bufferSize,
   521          `20 calls to append, only ${bufferSize} frames in the stats array`
   522        );
   523        assert.equal(
   524          task.memory.length,
   525          bufferSize,
   526          `20 calls to append, only ${bufferSize} frames in the stats array`
   527        );
   528      });
   529  
   530      assert.equal(
   531        +tracker.get('tasks').findBy('task', 'service').cpu[0].timestamp,
   532        +makeDate(refDate + 11),
   533        'Old frames are removed in favor of newer ones'
   534      );
   535      assert.equal(
   536        +tracker.get('tasks').findBy('task', 'service').memory[0].timestamp,
   537        +makeDate(refDate + 11),
   538        'Old frames are removed in favor of newer ones'
   539      );
   540  
   541      assert.equal(
   542        +tracker.get('tasks').findBy('task', 'log-shipper').cpu[0].timestamp,
   543        +makeDate(refDate + 110),
   544        'Old frames are removed in favor of newer ones'
   545      );
   546      assert.equal(
   547        +tracker.get('tasks').findBy('task', 'log-shipper').memory[0].timestamp,
   548        +makeDate(refDate + 110),
   549        'Old frames are removed in favor of newer ones'
   550      );
   551  
   552      assert.equal(
   553        +tracker.get('tasks').findBy('task', 'sidecar').cpu[0].timestamp,
   554        +makeDate(refDate + 1100),
   555        'Old frames are removed in favor of newer ones'
   556      );
   557      assert.equal(
   558        +tracker.get('tasks').findBy('task', 'sidecar').memory[0].timestamp,
   559        +makeDate(refDate + 1100),
   560        'Old frames are removed in favor of newer ones'
   561      );
   562    });
   563  
   564    test('the stats computed property macro constructs an AllocationStatsTracker based on an allocationProp and a fetch definition', async function (assert) {
   565      const allocation = MockAllocation();
   566      const fetchSpy = sinon.spy();
   567  
   568      const SomeClass = EmberObject.extend({
   569        stats: stats('alloc', function () {
   570          return () => fetchSpy(this);
   571        }),
   572      });
   573      const someObject = SomeClass.create({
   574        alloc: allocation,
   575      });
   576  
   577      assert.equal(
   578        someObject.get('stats.url'),
   579        `/v1/client/allocation/${allocation.id}/stats`,
   580        'stats computed property macro creates an AllocationStatsTracker'
   581      );
   582  
   583      someObject.get('stats').fetch();
   584  
   585      assert.ok(
   586        fetchSpy.calledWith(someObject),
   587        'the fetch factory passed into the macro gets called to assign a bound version of fetch to the AllocationStatsTracker instance'
   588      );
   589    });
   590  
   591    test('changing the value of the allocationProp constructs a new AllocationStatsTracker', async function (assert) {
   592      const alloc1 = MockAllocation();
   593      const alloc2 = MockAllocation();
   594      const SomeClass = EmberObject.extend({
   595        stats: stats('alloc', () => fetch),
   596      });
   597  
   598      const someObject = SomeClass.create({
   599        alloc: alloc1,
   600      });
   601  
   602      const stats1 = someObject.get('stats');
   603  
   604      someObject.set('alloc', alloc2);
   605      const stats2 = someObject.get('stats');
   606  
   607      assert.notStrictEqual(
   608        stats1,
   609        stats2,
   610        'Changing the value of alloc results in creating a new AllocationStatsTracker instance'
   611      );
   612    });
   613  
   614    statsTrackerFrameMissingBehavior({
   615      resourceName: 'allocation',
   616      ResourceConstructor: MockAllocation,
   617      TrackerConstructor: AllocationStatsTracker,
   618      mockFrame,
   619      compileResources(frame) {
   620        const timestamp = makeDate(frame.Timestamp);
   621        const cpu = frame.ResourceUsage.CpuStats.TotalTicks;
   622        const memory = frame.ResourceUsage.MemoryStats.RSS;
   623        return [
   624          {
   625            timestamp,
   626            used: cpu,
   627            percent: cpu / 200,
   628          },
   629          {
   630            timestamp,
   631            used: memory,
   632            percent: memory / 1024 / 1024 / 512,
   633          },
   634        ];
   635      },
   636    });
   637  });