File size: 2,957 Bytes
3484aa5
 
bf677e9
3484aa5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { cpus } from "node:os";
import { NS_runtime } from "@x/types/env.runtime";
import { NS_websockets } from "@x/ff-dl/ws";

const DEBUG_MEM = () =>
  (NS_runtime.ENV_VAR_DB?.DEBUG_MEM || process.env.DEBUG_MEM) === "1";

const STATS_CHANNEL = "server-stats";
const STATS_INTERVAL_MS = 2000;

let cpu_timer: ReturnType<typeof setInterval> | undefined;
let previous_cpu = process.cpuUsage();
let previous_time = process.hrtime.bigint();

const OS = () => process.platform;

const mb = (n: number) => Number((n / 1024 / 1024).toFixed(2));

function get_server_stats() {
  const now = process.hrtime.bigint();
  const elapsed_us = Number(now - previous_time) / 1000;

  const cpu = process.cpuUsage(previous_cpu);
  const cpu_time_us = cpu.user + cpu.system;

  // Percentage of one CPU core used by this process.
  const process_cpu_percent =
    elapsed_us > 0 ? (cpu_time_us / elapsed_us) * 100 : 0;

  // Percentage of total machine CPU capacity.
  const core_count = cpus().length;

  previous_cpu = process.cpuUsage();
  previous_time = now;

  const memory = process.memoryUsage();

  return {
    timestamp: Date.now(),
    OS: OS(),

    cpu: {
      process_percent: Number(process_cpu_percent.toFixed(2)),
      machine_percent: Number((process_cpu_percent / core_count).toFixed(2)),
      cores: core_count
    },

    memory: {
      rss_mb: mb(memory.rss),
      heap_used_mb: mb(memory.heapUsed),
      heap_total_mb: mb(memory.heapTotal),
      external_mb: mb(memory.external),
      array_buffers_mb: mb(memory.arrayBuffers)
    },

    process: {
      uptime_seconds: Math.floor(process.uptime()),
      pid: process.pid
    }
  };
}

function broadcast_server_stats() {
  const stats = get_server_stats();

  const payload = JSON.stringify({
    type: "SERVER-STATS",
    data: stats
  });

  for (const client of NS_websockets.ws_clients.values()) {
    if (!client.channels.has(STATS_CHANNEL)) continue;

    try {
      client.socket.send(payload);
    } catch (error) {
      if (!NS_runtime.IS_PROD()) {
        console.error("WS STATS SEND ERROR:", error);
      }
    }
  }
}

function start_cpu_timer() {
  if (cpu_timer) return;

  // Establish a fresh baseline before the first sample.
  previous_cpu = process.cpuUsage();
  previous_time = process.hrtime.bigint();

  if (DEBUG_MEM()) {
    cpu_timer = setInterval(broadcast_server_stats, STATS_INTERVAL_MS);
  }
}

function stop_cpu_timer() {
  if (!cpu_timer) return;

  clearInterval(cpu_timer);
  cpu_timer = undefined;
}

function has_stats_subscribers() {
  for (const client of NS_websockets.ws_clients.values()) {
    if (client.channels.has(STATS_CHANNEL)) {
      return true;
    }
  }

  return false;
}

function sync_cpu_timer() {
  if (has_stats_subscribers()) {
    start_cpu_timer();
  } else {
    stop_cpu_timer();
  }
}

export const NS_server_stats = {
  get: get_server_stats,
  start: start_cpu_timer,
  stop: stop_cpu_timer,
  sync: sync_cpu_timer
};