github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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  });