UNPKG

19.7 kBJavaScriptView Raw
1'use strict';
2// @ts-check
3// ==================================================================================
4// docker.js
5// ----------------------------------------------------------------------------------
6// Description: System Information - library
7// for Node.js
8// Copyright: (c) 2014 - 2020
9// Author: Sebastian Hildebrandt
10// ----------------------------------------------------------------------------------
11// License: MIT
12// ==================================================================================
13// 13. Docker
14// ----------------------------------------------------------------------------------
15
16const util = require('./util');
17const DockerSocket = require('./dockerSocket');
18
19let _platform = process.platform;
20const _windows = (_platform === 'win32');
21
22let _docker_container_stats = {};
23let _docker_socket;
24let _docker_last_read = 0;
25
26
27// --------------------------
28// get containers (parameter all: get also inactive/exited containers)
29
30function dockerInfo(callback) {
31 return new Promise((resolve) => {
32 process.nextTick(() => {
33 if (!_docker_socket) {
34 _docker_socket = new DockerSocket();
35 }
36 const result = {};
37
38 _docker_socket.getInfo(data => {
39 result.id = data.ID;
40 result.containers = data.Containers;
41 result.containersRunning = data.ContainersRunning;
42 result.containersPaused = data.ContainersPaused;
43 result.containersStopped = data.ContainersStopped;
44 result.images = data.Images;
45 result.driver = data.Driver;
46 result.memoryLimit = data.MemoryLimit;
47 result.swapLimit = data.SwapLimit;
48 result.kernelMemory = data.KernelMemory;
49 result.cpuCfsPeriod = data.CpuCfsPeriod;
50 result.cpuCfsQuota = data.CpuCfsQuota;
51 result.cpuShares = data.CPUShares;
52 result.cpuSet = data.CPUSet;
53 result.ipv4Forwarding = data.IPv4Forwarding;
54 result.bridgeNfIptables = data.BridgeNfIptables;
55 result.bridgeNfIp6tables = data.BridgeNfIp6tables;
56 result.debug = data.Debug;
57 result.nfd = data.NFd;
58 result.oomKillDisable = data.OomKillDisable;
59 result.ngoroutines = data.NGoroutines;
60 result.systemTime = data.SystemTime;
61 result.loggingDriver = data.LoggingDriver;
62 result.cgroupDriver = data.CgroupDriver;
63 result.nEventsListener = data.NEventsListener;
64 result.kernelVersion = data.KernelVersion;
65 result.operatingSystem = data.OperatingSystem;
66 result.osType = data.OSType;
67 result.architecture = data.Architecture;
68 result.ncpu = data.NCPU;
69 result.memTotal = data.MemTotal;
70 result.dockerRootDir = data.DockerRootDir;
71 result.httpProxy = data.HttpProxy;
72 result.httpsProxy = data.HttpsProxy;
73 result.noProxy = data.NoProxy;
74 result.name = data.Name;
75 result.labels = data.Labels;
76 result.experimentalBuild = data.ExperimentalBuild;
77 result.serverVersion = data.ServerVersion;
78 result.clusterStore = data.ClusterStore;
79 result.clusterAdvertise = data.ClusterAdvertise;
80 result.defaultRuntime = data.DefaultRuntime;
81 result.liveRestoreEnabled = data.LiveRestoreEnabled;
82 result.isolation = data.Isolation;
83 result.initBinary = data.InitBinary;
84 result.productLicense = data.ProductLicense;
85 if (callback) { callback(result); }
86 resolve(result);
87 });
88 });
89 });
90}
91
92exports.dockerInfo = dockerInfo;
93
94function dockerContainers(all, callback) {
95
96 function inContainers(containers, id) {
97 let filtered = containers.filter(obj => {
98 /**
99 * @namespace
100 * @property {string} Id
101 */
102 return (obj.Id && (obj.Id === id));
103 });
104 return (filtered.length > 0);
105 }
106
107 // fallback - if only callback is given
108 if (util.isFunction(all) && !callback) {
109 callback = all;
110 all = false;
111 }
112
113 all = all || false;
114 let result = [];
115 return new Promise((resolve) => {
116 process.nextTick(() => {
117 if (!_docker_socket) {
118 _docker_socket = new DockerSocket();
119 }
120 const workload = [];
121
122 _docker_socket.listContainers(all, data => {
123 let docker_containers = {};
124 try {
125 docker_containers = data;
126 if (docker_containers && Object.prototype.toString.call(docker_containers) === '[object Array]' && docker_containers.length > 0) {
127 // GC in _docker_container_stats
128 for (let key in _docker_container_stats) {
129 if ({}.hasOwnProperty.call(_docker_container_stats, key)) {
130 if (!inContainers(docker_containers, key)) delete _docker_container_stats[key];
131 }
132 }
133
134 docker_containers.forEach(function (element) {
135
136 if (element.Names && Object.prototype.toString.call(element.Names) === '[object Array]' && element.Names.length > 0) {
137 element.Name = element.Names[0].replace(/^\/|\/$/g, '');
138 }
139 workload.push(dockerContainerInspect(element.Id.trim(), element));
140 // result.push({
141 // id: element.Id,
142 // name: element.Name,
143 // image: element.Image,
144 // imageID: element.ImageID,
145 // command: element.Command,
146 // created: element.Created,
147 // state: element.State,
148 // ports: element.Ports,
149 // mounts: element.Mounts,
150 // // hostconfig: element.HostConfig,
151 // // network: element.NetworkSettings
152 // });
153 });
154 if (workload.length) {
155 Promise.all(
156 workload
157 ).then(data => {
158 if (callback) { callback(data); }
159 resolve(data);
160 });
161 } else {
162 if (callback) { callback(result); }
163 resolve(result);
164 }
165 }
166 } catch (err) {
167 // GC in _docker_container_stats
168 for (let key in _docker_container_stats) {
169 if ({}.hasOwnProperty.call(_docker_container_stats, key)) {
170 if (!inContainers(docker_containers, key)) delete _docker_container_stats[key];
171 }
172 }
173 if (callback) { callback(result); }
174 resolve(result);
175 }
176 });
177 });
178 });
179}
180
181// --------------------------
182// container inspect (for one container)
183
184function dockerContainerInspect(containerID, payload) {
185 containerID = containerID || '';
186 return new Promise((resolve) => {
187 process.nextTick(() => {
188 if (containerID) {
189
190 if (!_docker_socket) {
191 _docker_socket = new DockerSocket();
192 }
193
194 _docker_socket.getInspect(containerID.trim(), data => {
195 try {
196 resolve({
197 id: payload.Id,
198 name: payload.Name,
199 image: payload.Image,
200 imageID: payload.ImageID,
201 command: payload.Command,
202 created: payload.Created,
203 started: data.State && data.State.StartedAt ? Math.round(new Date(data.State.StartedAt).getTime() / 1000) : 0,
204 finished: data.State && data.State.FinishedAt && !data.State.FinishedAt.startsWith('0001-01-01') ? Math.round(new Date(data.State.FinishedAt).getTime() / 1000) : 0,
205 createdAt: data.Created ? data.Created : '',
206 startedAt: data.State && data.State.StartedAt ? data.State.StartedAt : '',
207 finishedAt: data.State && data.State.FinishedAt && !data.State.FinishedAt.startsWith('0001-01-01') ? data.State.FinishedAt : '',
208 state: payload.State,
209 restartCount: data.RestartCount || 0,
210 platform: data.Platform || '',
211 driver: data.Driver || '',
212 ports: payload.Ports,
213 mounts: payload.Mounts,
214 // hostconfig: payload.HostConfig,
215 // network: payload.NetworkSettings
216 });
217 } catch (err) {
218 resolve();
219 }
220 });
221 } else {
222 resolve();
223 }
224 });
225 });
226}
227
228exports.dockerContainers = dockerContainers;
229
230// --------------------------
231// helper functions for calculation of docker stats
232
233function docker_calcCPUPercent(cpu_stats, precpu_stats) {
234 /**
235 * @namespace
236 * @property {object} cpu_usage
237 * @property {number} cpu_usage.total_usage
238 * @property {number} system_cpu_usage
239 * @property {object} cpu_usage
240 * @property {Array} cpu_usage.percpu_usage
241 */
242
243 if (!_windows) {
244 let cpuPercent = 0.0;
245 // calculate the change for the cpu usage of the container in between readings
246 let cpuDelta = cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
247 // calculate the change for the entire system between readings
248 let systemDelta = cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage;
249
250 if (systemDelta > 0.0 && cpuDelta > 0.0) {
251 // calculate the change for the cpu usage of the container in between readings
252 cpuPercent = (cpuDelta / systemDelta) * cpu_stats.cpu_usage.percpu_usage.length * 100.0;
253 }
254
255 return cpuPercent;
256 } else {
257 let nanoSecNow = util.nanoSeconds();
258 let cpuPercent = 0.0;
259 if (_docker_last_read > 0) {
260 let possIntervals = (nanoSecNow - _docker_last_read); // / 100 * os.cpus().length;
261 let intervalsUsed = cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
262 if (possIntervals > 0) {
263 cpuPercent = 100.0 * intervalsUsed / possIntervals;
264 }
265 }
266 _docker_last_read = nanoSecNow;
267 return cpuPercent;
268 }
269}
270
271function docker_calcNetworkIO(networks) {
272 let rx;
273 let tx;
274 for (let key in networks) {
275 // skip loop if the property is from prototype
276 if (!{}.hasOwnProperty.call(networks, key)) continue;
277
278 /**
279 * @namespace
280 * @property {number} rx_bytes
281 * @property {number} tx_bytes
282 */
283 let obj = networks[key];
284 rx = +obj.rx_bytes;
285 tx = +obj.tx_bytes;
286 }
287 return {
288 rx: rx,
289 tx: tx
290 };
291}
292
293function docker_calcBlockIO(blkio_stats) {
294 let result = {
295 r: 0,
296 w: 0
297 };
298
299 /**
300 * @namespace
301 * @property {Array} io_service_bytes_recursive
302 */
303 if (blkio_stats && blkio_stats.io_service_bytes_recursive && Object.prototype.toString.call(blkio_stats.io_service_bytes_recursive) === '[object Array]' && blkio_stats.io_service_bytes_recursive.length > 0) {
304 blkio_stats.io_service_bytes_recursive.forEach(function (element) {
305 /**
306 * @namespace
307 * @property {string} op
308 * @property {number} value
309 */
310
311 if (element.op && element.op.toLowerCase() === 'read' && element.value) {
312 result.r += element.value;
313 }
314 if (element.op && element.op.toLowerCase() === 'write' && element.value) {
315 result.w += element.value;
316 }
317 });
318 }
319 return result;
320}
321
322function dockerContainerStats(containerIDs, callback) {
323
324 let containerArray = [];
325 // fallback - if only callback is given
326 if (util.isFunction(containerIDs) && !callback) {
327 callback = containerIDs;
328 containerArray = ['*'];
329 } else {
330 containerIDs = containerIDs || '*';
331 containerIDs = containerIDs.trim().toLowerCase().replace(/,+/g, '|');
332 containerArray = containerIDs.split('|');
333 }
334
335 return new Promise((resolve) => {
336 process.nextTick(() => {
337
338 const result = [];
339
340 const workload = [];
341 if (containerArray.length && containerArray[0].trim() === '*') {
342 containerArray = [];
343 dockerContainers().then(allContainers => {
344 for (let container of allContainers) {
345 containerArray.push(container.id);
346 }
347 dockerContainerStats(containerArray.join(',')).then(result => {
348 if (callback) { callback(result); }
349 resolve(result);
350 });
351 });
352 } else {
353 for (let containerID of containerArray) {
354 workload.push(dockerContainerStatsSingle(containerID.trim()));
355 }
356 if (workload.length) {
357 Promise.all(
358 workload
359 ).then(data => {
360 if (callback) { callback(data); }
361 resolve(data);
362 });
363 } else {
364 if (callback) { callback(result); }
365 resolve(result);
366 }
367 }
368 });
369 });
370}
371
372// --------------------------
373// container stats (for one container)
374
375function dockerContainerStatsSingle(containerID) {
376 containerID = containerID || '';
377 let result = {
378 id: containerID,
379 mem_usage: 0,
380 mem_limit: 0,
381 mem_percent: 0,
382 cpu_percent: 0,
383 pids: 0,
384 netIO: {
385 rx: 0,
386 wx: 0
387 },
388 blockIO: {
389 r: 0,
390 w: 0
391 }
392 };
393 return new Promise((resolve) => {
394 process.nextTick(() => {
395 if (containerID) {
396
397 if (!_docker_socket) {
398 _docker_socket = new DockerSocket();
399 }
400
401 _docker_socket.getInspect(containerID, dataInspect => {
402 try {
403 _docker_socket.getStats(containerID, data => {
404 try {
405 let stats = data;
406 /**
407 * @namespace
408 * @property {Object} memory_stats
409 * @property {number} memory_stats.usage
410 * @property {number} memory_stats.limit
411 * @property {Object} cpu_stats
412 * @property {Object} pids_stats
413 * @property {number} pids_stats.current
414 * @property {Object} networks
415 * @property {Object} blkio_stats
416 */
417
418 if (!stats.message) {
419 result.mem_usage = (stats.memory_stats && stats.memory_stats.usage ? stats.memory_stats.usage : 0);
420 result.mem_limit = (stats.memory_stats && stats.memory_stats.limit ? stats.memory_stats.limit : 0);
421 result.mem_percent = (stats.memory_stats && stats.memory_stats.usage && stats.memory_stats.limit ? stats.memory_stats.usage / stats.memory_stats.limit * 100.0 : 0);
422 result.cpu_percent = (stats.cpu_stats && stats.precpu_stats ? docker_calcCPUPercent(stats.cpu_stats, stats.precpu_stats) : 0);
423 result.pids = (stats.pids_stats && stats.pids_stats.current ? stats.pids_stats.current : 0);
424 result.restartCount = (dataInspect.RestartCount ? dataInspect.RestartCount : 0);
425 if (stats.networks) result.netIO = docker_calcNetworkIO(stats.networks);
426 if (stats.blkio_stats) result.blockIO = docker_calcBlockIO(stats.blkio_stats);
427 result.cpu_stats = (stats.cpu_stats ? stats.cpu_stats : {});
428 result.precpu_stats = (stats.precpu_stats ? stats.precpu_stats : {});
429 result.memory_stats = (stats.memory_stats ? stats.memory_stats : {});
430 result.networks = (stats.networks ? stats.networks : {});
431 }
432 } catch (err) {
433 util.noop();
434 }
435 // }
436 resolve(result);
437 });
438 } catch (err) {
439 util.noop();
440 }
441 });
442 } else {
443 resolve(result);
444 }
445 });
446 });
447}
448
449exports.dockerContainerStats = dockerContainerStats;
450
451// --------------------------
452// container processes (for one container)
453
454function dockerContainerProcesses(containerID, callback) {
455 containerID = containerID || '';
456 let result = [];
457 return new Promise((resolve) => {
458 process.nextTick(() => {
459 if (containerID) {
460
461 if (!_docker_socket) {
462 _docker_socket = new DockerSocket();
463 }
464
465 _docker_socket.getProcesses(containerID, data => {
466 /**
467 * @namespace
468 * @property {Array} Titles
469 * @property {Array} Processes
470 **/
471 try {
472 if (data && data.Titles && data.Processes) {
473 let titles = data.Titles.map(function (value) {
474 return value.toUpperCase();
475 });
476 let pos_pid = titles.indexOf('PID');
477 let pos_ppid = titles.indexOf('PPID');
478 let pos_pgid = titles.indexOf('PGID');
479 let pos_vsz = titles.indexOf('VSZ');
480 let pos_time = titles.indexOf('TIME');
481 let pos_elapsed = titles.indexOf('ELAPSED');
482 let pos_ni = titles.indexOf('NI');
483 let pos_ruser = titles.indexOf('RUSER');
484 let pos_user = titles.indexOf('USER');
485 let pos_rgroup = titles.indexOf('RGROUP');
486 let pos_group = titles.indexOf('GROUP');
487 let pos_stat = titles.indexOf('STAT');
488 let pos_rss = titles.indexOf('RSS');
489 let pos_command = titles.indexOf('COMMAND');
490
491 data.Processes.forEach(process => {
492 result.push({
493 pid_host: (pos_pid >= 0 ? process[pos_pid] : ''),
494 ppid: (pos_ppid >= 0 ? process[pos_ppid] : ''),
495 pgid: (pos_pgid >= 0 ? process[pos_pgid] : ''),
496 user: (pos_user >= 0 ? process[pos_user] : ''),
497 ruser: (pos_ruser >= 0 ? process[pos_ruser] : ''),
498 group: (pos_group >= 0 ? process[pos_group] : ''),
499 rgroup: (pos_rgroup >= 0 ? process[pos_rgroup] : ''),
500 stat: (pos_stat >= 0 ? process[pos_stat] : ''),
501 time: (pos_time >= 0 ? process[pos_time] : ''),
502 elapsed: (pos_elapsed >= 0 ? process[pos_elapsed] : ''),
503 nice: (pos_ni >= 0 ? process[pos_ni] : ''),
504 rss: (pos_rss >= 0 ? process[pos_rss] : ''),
505 vsz: (pos_vsz >= 0 ? process[pos_vsz] : ''),
506 command: (pos_command >= 0 ? process[pos_command] : '')
507 });
508 });
509 }
510 } catch (err) {
511 util.noop();
512 }
513 if (callback) { callback(result); }
514 resolve(result);
515 });
516 } else {
517 if (callback) { callback(result); }
518 resolve(result);
519 }
520 });
521 });
522}
523
524exports.dockerContainerProcesses = dockerContainerProcesses;
525
526function dockerAll(callback) {
527 return new Promise((resolve) => {
528 process.nextTick(() => {
529 dockerContainers(true).then(result => {
530 if (result && Object.prototype.toString.call(result) === '[object Array]' && result.length > 0) {
531 let l = result.length;
532 result.forEach(function (element) {
533 dockerContainerStats(element.id).then(res => {
534 // include stats in array
535 element.mem_usage = res[0].mem_usage;
536 element.mem_limit = res[0].mem_limit;
537 element.mem_percent = res[0].mem_percent;
538 element.cpu_percent = res[0].cpu_percent;
539 element.pids = res[0].pids;
540 element.netIO = res[0].netIO;
541 element.blockIO = res[0].blockIO;
542 element.cpu_stats = res[0].cpu_stats;
543 element.precpu_stats = res[0].precpu_stats;
544 element.memory_stats = res[0].memory_stats;
545 element.networks = res[0].networks;
546
547 dockerContainerProcesses(element.id).then(processes => {
548 element.processes = processes;
549
550 l -= 1;
551 if (l === 0) {
552 if (callback) { callback(result); }
553 resolve(result);
554 }
555 });
556 // all done??
557 });
558 });
559 } else {
560 if (callback) { callback(result); }
561 resolve(result);
562 }
563 });
564 });
565 });
566}
567
568exports.dockerAll = dockerAll;