github.com/hernad/nomad@v1.6.112/ui/app/utils/classes/allocation-stats-tracker.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 import EmberObject, { get, computed } from '@ember/object'; 7 import { alias } from '@ember/object/computed'; 8 import RollingArray from 'nomad-ui/utils/classes/rolling-array'; 9 import AbstractStatsTracker from 'nomad-ui/utils/classes/abstract-stats-tracker'; 10 import classic from 'ember-classic-decorator'; 11 12 const percent = (numerator, denominator) => { 13 if (!numerator || !denominator) { 14 return 0; 15 } 16 return numerator / denominator; 17 }; 18 19 const empty = (ts) => ({ timestamp: ts, used: null, percent: null }); 20 21 // Tasks are sorted by their lifecycle phase in this order: 22 const sortMap = [ 23 'main', 24 'prestart-sidecar', 25 'poststart-sidecar', 26 'prestart-ephemeral', 27 'poststart-ephemeral', 28 'poststop', 29 ].reduce((map, phase, index) => { 30 map[phase] = index; 31 return map; 32 }, {}); 33 34 const taskPrioritySort = (a, b) => 35 sortMap[a.lifecycleName] - sortMap[b.lifecycleName]; 36 37 // Select the value for memory usage. 38 // Must match logic in command/alloc_status.go. 39 const memoryUsed = (frame) => 40 frame.ResourceUsage.MemoryStats.RSS || 41 frame.ResourceUsage.MemoryStats.Usage || 42 0; 43 44 @classic 45 class AllocationStatsTracker extends EmberObject.extend(AbstractStatsTracker) { 46 // Set via the stats computed property macro 47 allocation = null; 48 49 @computed('allocation.id') 50 get url() { 51 return `/v1/client/allocation/${this.get('allocation.id')}/stats`; 52 } 53 54 append(frame) { 55 const timestamp = new Date(Math.floor(frame.Timestamp / 1000000)); 56 57 const cpuUsed = Math.floor(frame.ResourceUsage.CpuStats.TotalTicks) || 0; 58 this.cpu.pushObject({ 59 timestamp, 60 used: cpuUsed, 61 percent: percent(cpuUsed, this.reservedCPU), 62 }); 63 64 const memUsed = memoryUsed(frame); 65 this.memory.pushObject({ 66 timestamp, 67 used: memUsed, 68 percent: percent(memUsed / 1024 / 1024, this.reservedMemory), 69 }); 70 71 let aggregateCpu = 0; 72 let aggregateMemory = 0; 73 for (var stats of this.tasks) { 74 const taskFrame = frame.Tasks[stats.task]; 75 76 // If the task is not present in the frame data (because it hasn't started or 77 // it has already stopped), just keep going. 78 if (!taskFrame) continue; 79 80 const frameTimestamp = new Date( 81 Math.floor(taskFrame.Timestamp / 1000000) 82 ); 83 84 const taskCpuUsed = 85 Math.floor(taskFrame.ResourceUsage.CpuStats.TotalTicks) || 0; 86 const percentCpuTotal = percent(taskCpuUsed, this.reservedCPU); 87 stats.cpu.pushObject({ 88 timestamp: frameTimestamp, 89 used: taskCpuUsed, 90 percent: percent(taskCpuUsed, stats.reservedCPU), 91 percentTotal: percentCpuTotal, 92 percentStack: percentCpuTotal + aggregateCpu, 93 }); 94 95 const taskMemoryUsed = memoryUsed(taskFrame); 96 const percentMemoryTotal = percent( 97 taskMemoryUsed / 1024 / 1024, 98 this.reservedMemory 99 ); 100 stats.memory.pushObject({ 101 timestamp: frameTimestamp, 102 used: taskMemoryUsed, 103 percent: percent(taskMemoryUsed / 1024 / 1024, stats.reservedMemory), 104 percentTotal: percentMemoryTotal, 105 percentStack: percentMemoryTotal + aggregateMemory, 106 }); 107 108 aggregateCpu += percentCpuTotal; 109 aggregateMemory += percentMemoryTotal; 110 } 111 } 112 113 pause() { 114 const ts = new Date(); 115 this.memory.pushObject(empty(ts)); 116 this.cpu.pushObject(empty(ts)); 117 this.tasks.forEach((task) => { 118 task.memory.pushObject(empty(ts)); 119 task.cpu.pushObject(empty(ts)); 120 }); 121 } 122 123 // Static figures, denominators for stats 124 @alias('allocation.taskGroup.reservedCPU') reservedCPU; 125 @alias('allocation.taskGroup.reservedMemory') reservedMemory; 126 127 // Dynamic figures, collected over time 128 // []{ timestamp: Date, used: Number, percent: Number } 129 @computed('allocation', 'bufferSize') 130 get cpu() { 131 return RollingArray(this.bufferSize); 132 } 133 134 @computed('allocation', 'bufferSize') 135 get memory() { 136 return RollingArray(this.bufferSize); 137 } 138 139 @computed('allocation.taskGroup.tasks', 'bufferSize') 140 get tasks() { 141 const bufferSize = this.bufferSize; 142 const tasks = this.get('allocation.taskGroup.tasks') || []; 143 return tasks 144 .slice() 145 .sort(taskPrioritySort) 146 .map((task) => ({ 147 task: get(task, 'name'), 148 149 // Static figures, denominators for stats 150 reservedCPU: get(task, 'reservedCPU'), 151 reservedMemory: get(task, 'reservedMemory'), 152 153 // Dynamic figures, collected over time 154 // []{ timestamp: Date, used: Number, percent: Number } 155 cpu: RollingArray(bufferSize), 156 memory: RollingArray(bufferSize), 157 })); 158 } 159 } 160 161 export default AllocationStatsTracker; 162 163 export function stats(allocationProp, fetch) { 164 return computed(allocationProp, function () { 165 return AllocationStatsTracker.create({ 166 fetch: fetch.call(this), 167 allocation: this.get(allocationProp), 168 }); 169 }); 170 }