UNPKG

19.8 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 } else {
166 if (callback) { callback(result); }
167 resolve(result);
168 }
169 } catch (err) {
170 // GC in _docker_container_stats
171 for (let key in _docker_container_stats) {
172 if ({}.hasOwnProperty.call(_docker_container_stats, key)) {
173 if (!inContainers(docker_containers, key)) delete _docker_container_stats[key];
174 }
175 }
176 if (callback) { callback(result); }
177 resolve(result);
178 }
179 });
180 });
181 });
182}
183
184// --------------------------
185// container inspect (for one container)
186
187function dockerContainerInspect(containerID, payload) {
188 containerID = containerID || '';
189 return new Promise((resolve) => {
190 process.nextTick(() => {
191 if (containerID) {
192
193 if (!_docker_socket) {
194 _docker_socket = new DockerSocket();
195 }
196
197 _docker_socket.getInspect(containerID.trim(), data => {
198 try {
199 resolve({
200 id: payload.Id,
201 name: payload.Name,
202 image: payload.Image,
203 imageID: payload.ImageID,
204 command: payload.Command,
205 created: payload.Created,
206 started: data.State && data.State.StartedAt ? Math.round(new Date(data.State.StartedAt).getTime() / 1000) : 0,
207 finished: data.State && data.State.FinishedAt && !data.State.FinishedAt.startsWith('0001-01-01') ? Math.round(new Date(data.State.FinishedAt).getTime() / 1000) : 0,
208 createdAt: data.Created ? data.Created : '',
209 startedAt: data.State && data.State.StartedAt ? data.State.StartedAt : '',
210 finishedAt: data.State && data.State.FinishedAt && !data.State.FinishedAt.startsWith('0001-01-01') ? data.State.FinishedAt : '',
211 state: payload.State,
212 restartCount: data.RestartCount || 0,
213 platform: data.Platform || '',
214 driver: data.Driver || '',
215 ports: payload.Ports,
216 mounts: payload.Mounts,
217 // hostconfig: payload.HostConfig,
218 // network: payload.NetworkSettings
219 });
220 } catch (err) {
221 resolve();
222 }
223 });
224 } else {
225 resolve();
226 }
227 });
228 });
229}
230
231exports.dockerContainers = dockerContainers;
232
233// --------------------------
234// helper functions for calculation of docker stats
235
236function docker_calcCPUPercent(cpu_stats, precpu_stats) {
237 /**
238 * @namespace
239 * @property {object} cpu_usage
240 * @property {number} cpu_usage.total_usage
241 * @property {number} system_cpu_usage
242 * @property {object} cpu_usage
243 * @property {Array} cpu_usage.percpu_usage
244 */
245
246 if (!_windows) {
247 let cpuPercent = 0.0;
248 // calculate the change for the cpu usage of the container in between readings
249 let cpuDelta = cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
250 // calculate the change for the entire system between readings
251 let systemDelta = cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage;
252
253 if (systemDelta > 0.0 && cpuDelta > 0.0) {
254 // calculate the change for the cpu usage of the container in between readings
255 cpuPercent = (cpuDelta / systemDelta) * cpu_stats.cpu_usage.percpu_usage.length * 100.0;
256 }
257
258 return cpuPercent;
259 } else {
260 let nanoSecNow = util.nanoSeconds();
261 let cpuPercent = 0.0;
262 if (_docker_last_read > 0) {
263 let possIntervals = (nanoSecNow - _docker_last_read); // / 100 * os.cpus().length;
264 let intervalsUsed = cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
265 if (possIntervals > 0) {
266 cpuPercent = 100.0 * intervalsUsed / possIntervals;
267 }
268 }
269 _docker_last_read = nanoSecNow;
270 return cpuPercent;
271 }
272}
273
274function docker_calcNetworkIO(networks) {
275 let rx;
276 let tx;
277 for (let key in networks) {
278 // skip loop if the property is from prototype
279 if (!{}.hasOwnProperty.call(networks, key)) continue;
280
281 /**
282 * @namespace
283 * @property {number} rx_bytes
284 * @property {number} tx_bytes
285 */
286 let obj = networks[key];
287 rx = +obj.rx_bytes;
288 tx = +obj.tx_bytes;
289 }
290 return {
291 rx: rx,
292 tx: tx
293 };
294}
295
296function docker_calcBlockIO(blkio_stats) {
297 let result = {
298 r: 0,
299 w: 0
300 };
301
302 /**
303 * @namespace
304 * @property {Array} io_service_bytes_recursive
305 */
306 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) {
307 blkio_stats.io_service_bytes_recursive.forEach(function (element) {
308 /**
309 * @namespace
310 * @property {string} op
311 * @property {number} value
312 */
313
314 if (element.op && element.op.toLowerCase() === 'read' && element.value) {
315 result.r += element.value;
316 }
317 if (element.op && element.op.toLowerCase() === 'write' && element.value) {
318 result.w += element.value;
319 }
320 });
321 }
322 return result;
323}
324
325function dockerContainerStats(containerIDs, callback) {
326
327 let containerArray = [];
328 // fallback - if only callback is given
329 if (util.isFunction(containerIDs) && !callback) {
330 callback = containerIDs;
331 containerArray = ['*'];
332 } else {
333 containerIDs = containerIDs || '*';
334 containerIDs = containerIDs.trim().toLowerCase().replace(/,+/g, '|');
335 containerArray = containerIDs.split('|');
336 }
337
338 return new Promise((resolve) => {
339 process.nextTick(() => {
340
341 const result = [];
342
343 const workload = [];
344 if (containerArray.length && containerArray[0].trim() === '*') {
345 containerArray = [];
346 dockerContainers().then(allContainers => {
347 for (let container of allContainers) {
348 containerArray.push(container.id);
349 }
350 dockerContainerStats(containerArray.join(',')).then(result => {
351 if (callback) { callback(result); }
352 resolve(result);
353 });
354 });
355 } else {
356 for (let containerID of containerArray) {
357 workload.push(dockerContainerStatsSingle(containerID.trim()));
358 }
359 if (workload.length) {
360 Promise.all(
361 workload
362 ).then(data => {
363 if (callback) { callback(data); }
364 resolve(data);
365 });
366 } else {
367 if (callback) { callback(result); }
368 resolve(result);
369 }
370 }
371 });
372 });
373}
374
375// --------------------------
376// container stats (for one container)
377
378function dockerContainerStatsSingle(containerID) {
379 containerID = containerID || '';
380 let result = {
381 id: containerID,
382 mem_usage: 0,
383 mem_limit: 0,
384 mem_percent: 0,
385 cpu_percent: 0,
386 pids: 0,
387 netIO: {
388 rx: 0,
389 wx: 0
390 },
391 blockIO: {
392 r: 0,
393 w: 0
394 }
395 };
396 return new Promise((resolve) => {
397 process.nextTick(() => {
398 if (containerID) {
399
400 if (!_docker_socket) {
401 _docker_socket = new DockerSocket();
402 }
403
404 _docker_socket.getInspect(containerID, dataInspect => {
405 try {
406 _docker_socket.getStats(containerID, data => {
407 try {
408 let stats = data;
409 /**
410 * @namespace
411 * @property {Object} memory_stats
412 * @property {number} memory_stats.usage
413 * @property {number} memory_stats.limit
414 * @property {Object} cpu_stats
415 * @property {Object} pids_stats
416 * @property {number} pids_stats.current
417 * @property {Object} networks
418 * @property {Object} blkio_stats
419 */
420
421 if (!stats.message) {
422 result.mem_usage = (stats.memory_stats && stats.memory_stats.usage ? stats.memory_stats.usage : 0);
423 result.mem_limit = (stats.memory_stats && stats.memory_stats.limit ? stats.memory_stats.limit : 0);
424 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);
425 result.cpu_percent = (stats.cpu_stats && stats.precpu_stats ? docker_calcCPUPercent(stats.cpu_stats, stats.precpu_stats) : 0);
426 result.pids = (stats.pids_stats && stats.pids_stats.current ? stats.pids_stats.current : 0);
427 result.restartCount = (dataInspect.RestartCount ? dataInspect.RestartCount : 0);
428 if (stats.networks) result.netIO = docker_calcNetworkIO(stats.networks);
429 if (stats.blkio_stats) result.blockIO = docker_calcBlockIO(stats.blkio_stats);
430 result.cpu_stats = (stats.cpu_stats ? stats.cpu_stats : {});
431 result.precpu_stats = (stats.precpu_stats ? stats.precpu_stats : {});
432 result.memory_stats = (stats.memory_stats ? stats.memory_stats : {});
433 result.networks = (stats.networks ? stats.networks : {});
434 }
435 } catch (err) {
436 util.noop();
437 }
438 // }
439 resolve(result);
440 });
441 } catch (err) {
442 util.noop();
443 }
444 });
445 } else {
446 resolve(result);
447 }
448 });
449 });
450}
451
452exports.dockerContainerStats = dockerContainerStats;
453
454// --------------------------
455// container processes (for one container)
456
457function dockerContainerProcesses(containerID, callback) {
458 containerID = containerID || '';
459 let result = [];
460 return new Promise((resolve) => {
461 process.nextTick(() => {
462 if (containerID) {
463
464 if (!_docker_socket) {
465 _docker_socket = new DockerSocket();
466 }
467
468 _docker_socket.getProcesses(containerID, data => {
469 /**
470 * @namespace
471 * @property {Array} Titles
472 * @property {Array} Processes
473 **/
474 try {
475 if (data && data.Titles && data.Processes) {
476 let titles = data.Titles.map(function (value) {
477 return value.toUpperCase();
478 });
479 let pos_pid = titles.indexOf('PID');
480 let pos_ppid = titles.indexOf('PPID');
481 let pos_pgid = titles.indexOf('PGID');
482 let pos_vsz = titles.indexOf('VSZ');
483 let pos_time = titles.indexOf('TIME');
484 let pos_elapsed = titles.indexOf('ELAPSED');
485 let pos_ni = titles.indexOf('NI');
486 let pos_ruser = titles.indexOf('RUSER');
487 let pos_user = titles.indexOf('USER');
488 let pos_rgroup = titles.indexOf('RGROUP');
489 let pos_group = titles.indexOf('GROUP');
490 let pos_stat = titles.indexOf('STAT');
491 let pos_rss = titles.indexOf('RSS');
492 let pos_command = titles.indexOf('COMMAND');
493
494 data.Processes.forEach(process => {
495 result.push({
496 pid_host: (pos_pid >= 0 ? process[pos_pid] : ''),
497 ppid: (pos_ppid >= 0 ? process[pos_ppid] : ''),
498 pgid: (pos_pgid >= 0 ? process[pos_pgid] : ''),
499 user: (pos_user >= 0 ? process[pos_user] : ''),
500 ruser: (pos_ruser >= 0 ? process[pos_ruser] : ''),
501 group: (pos_group >= 0 ? process[pos_group] : ''),
502 rgroup: (pos_rgroup >= 0 ? process[pos_rgroup] : ''),
503 stat: (pos_stat >= 0 ? process[pos_stat] : ''),
504 time: (pos_time >= 0 ? process[pos_time] : ''),
505 elapsed: (pos_elapsed >= 0 ? process[pos_elapsed] : ''),
506 nice: (pos_ni >= 0 ? process[pos_ni] : ''),
507 rss: (pos_rss >= 0 ? process[pos_rss] : ''),
508 vsz: (pos_vsz >= 0 ? process[pos_vsz] : ''),
509 command: (pos_command >= 0 ? process[pos_command] : '')
510 });
511 });
512 }
513 } catch (err) {
514 util.noop();
515 }
516 if (callback) { callback(result); }
517 resolve(result);
518 });
519 } else {
520 if (callback) { callback(result); }
521 resolve(result);
522 }
523 });
524 });
525}
526
527exports.dockerContainerProcesses = dockerContainerProcesses;
528
529function dockerAll(callback) {
530 return new Promise((resolve) => {
531 process.nextTick(() => {
532 dockerContainers(true).then(result => {
533 if (result && Object.prototype.toString.call(result) === '[object Array]' && result.length > 0) {
534 let l = result.length;
535 result.forEach(function (element) {
536 dockerContainerStats(element.id).then(res => {
537 // include stats in array
538 element.mem_usage = res[0].mem_usage;
539 element.mem_limit = res[0].mem_limit;
540 element.mem_percent = res[0].mem_percent;
541 element.cpu_percent = res[0].cpu_percent;
542 element.pids = res[0].pids;
543 element.netIO = res[0].netIO;
544 element.blockIO = res[0].blockIO;
545 element.cpu_stats = res[0].cpu_stats;
546 element.precpu_stats = res[0].precpu_stats;
547 element.memory_stats = res[0].memory_stats;
548 element.networks = res[0].networks;
549
550 dockerContainerProcesses(element.id).then(processes => {
551 element.processes = processes;
552
553 l -= 1;
554 if (l === 0) {
555 if (callback) { callback(result); }
556 resolve(result);
557 }
558 });
559 // all done??
560 });
561 });
562 } else {
563 if (callback) { callback(result); }
564 resolve(result);
565 }
566 });
567 });
568 });
569}
570
571exports.dockerAll = dockerAll;