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  });