github.com/hernad/nomad@v1.6.112/ui/tests/unit/utils/node-stats-tracker-test.js (about)

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