fixed prometheus expoter classes with linting

This commit is contained in:
Vittorio Palmisano 2021-05-07 13:52:59 +02:00
parent 2f04658213
commit 3e61780394
6 changed files with 515 additions and 497 deletions

View file

@ -6,3 +6,7 @@ listeningPort: 3443
listeningRedirectPort: 0
httpOnly: false
trustProxy: ''
prometheus:
enabled: true
listen: 0.0.0.0

View file

@ -335,39 +335,45 @@ const configSchema = convict({
},
// Prometheus exporter
prometheus : {
deidentify : {
doc : 'De-identify IP addresses in Prometheus logs.',
enabled : {
doc : 'Enables the Prometheus metrics exporter.',
format : 'Boolean',
default : false
},
listen : {
doc : 'Prometheus exporter listening address.',
doc : 'Prometheus metrics exporter listening address.',
format : 'String',
default : 'localhost'
},
port : {
doc : 'The Prometheus metrics exporter listening port.',
format : 'port',
default : 8889
},
// default metrics options
deidentify : {
doc : 'De-identify IP addresses in Prometheus logs.',
format : 'Boolean',
default : false
},
numeric : {
doc : 'Show numeric IP addresses in Prometheus logs.',
format : 'Boolean',
default : false
},
port : {
doc : 'The Prometheus exporter listening port.',
format : 'port',
default : 8889
},
quiet : {
doc : 'Include fewer labels in Prometheus logs.',
doc : 'Include fewer labels in Prometheus metrics.',
format : 'Boolean',
default : false
},
// aggregated metrics options
period : {
doc : 'The Prometheus exporter update period (seconds).',
doc : 'The Prometheus metrics exporter update period (seconds).',
format : 'nat',
default : 15
},
secret : {
doc : 'The Prometheus exporter authorization header: `Bearer <secret>` required to allow scraping.',
doc : 'The Prometheus metrics exporter authorization header: `Bearer <secret>` required to allow scraping.',
format : String,
default : ''
}

View file

@ -9,83 +9,88 @@ const logger = new Logger('metrics:aggregated');
//
module.exports = function(workers, rooms_, peers_, config)
{
const register = new promClient.Registry();
promClient.collectDefaultMetrics({ prefix: 'mediasoup_', register });
const mediasoupStats = {};
let mediasoupStatsUpdate = 0;
const register = new promClient.Registry();
const formatStats = (s) =>
{
return {
length: s.length || 0,
sum: s.sum || 0,
mean: s.amean() || 0,
stddev: s.stddev() || 0,
p25: s.percentile(25) || 0,
min: s.min || 0,
max: s.max || 0,
};
};
promClient.collectDefaultMetrics({ prefix: 'mediasoup_', register });
const collectStats = async () =>
{
const now = Date.now();
const mediasoupStats = {};
let mediasoupStatsUpdate = 0;
if (now - mediasoupStatsUpdate < config.period * 1000)
{
return;
}
mediasoupStatsUpdate = now;
const formatStats = (s) =>
{
return {
length : s.length || 0,
sum : s.sum || 0,
mean : s.amean() || 0,
stddev : s.stddev() || 0,
p25 : s.percentile(25) || 0,
min : s.min || 0,
max : s.max || 0
};
};
const start = process.hrtime();
const collectStats = async () =>
{
const now = Date.now();
let workers_cpu = new Stats();
let workers_memory = new Stats();
if (now - mediasoupStatsUpdate < config.period * 1000)
{
return;
}
mediasoupStatsUpdate = now;
let rooms = new Stats();
rooms.push(rooms_.size);
const start = process.hrtime();
let peers = new Stats();
peers.push(peers_.size);
const workersCpu = new Stats();
const workersMemory = new Stats();
// in
let video_bitrates_in = new Stats();
let audio_bitrates_in = new Stats();
let video_scores_in = new Stats();
let audio_scores_in = new Stats();
const rooms = new Stats();
let packets_counts_in = new Stats();
let packets_losts_in = new Stats();
let packets_retransmitted_in = new Stats();
rooms.push(rooms_.size);
// out
let video_bitrates_out = new Stats();
let audio_bitrates_out = new Stats();
const peers = new Stats();
let round_trip_times_out = new Stats();
let packets_counts_out = new Stats();
let packets_losts_out = new Stats();
peers.push(peers_.size);
let spatial_layers_out = new Stats();
let temporal_layers_out = new Stats();
// in
const videoBitratesIn = new Stats();
const audioBitratesIn = new Stats();
const videoScoresIn = new Stats();
const audioScoresIn = new Stats();
try {
// iterate workers
for (const worker of workers.values())
{
// worker process stats
const workerStats = await pidusage(worker._pid);
workers_cpu.push(workerStats.cpu / 100);
workers_memory.push(workerStats.memory);
// iterate routers
for (const router of worker._routers.values())
{
// iterate transports
for (const transport of router._transports.values())
{
/* let stats = [];
const packetsCountsIn = new Stats();
const packetsLostsIn = new Stats();
const packetsRetransmittedIn = new Stats();
// out
const videoBitratesOut = new Stats();
const audioBitratesOut = new Stats();
const roundTripTimesOut = new Stats();
const packetsCountsOut = new Stats();
const packetsLostsOut = new Stats();
const spatialLayersOut = new Stats();
const temporalLayersOut = new Stats();
try
{
// iterate workers
for (const worker of workers)
{
// worker process stats
const workerStats = await pidusage(worker.pid);
workersCpu.push(workerStats.cpu / 100);
workersMemory.push(workerStats.memory);
// iterate routers
for (const router of worker._routers.values())
{
// iterate transports
for (const transport of router._transports.values())
{
/* let stats = [];
try
{
stats = await transport.getStats();
@ -102,212 +107,225 @@ module.exports = function(workers, rooms_, peers_, config)
}
} */
// iterate producers
for (const producer of transport._producers.values())
{
let stats = [];
try
{
stats = await producer.getStats();
}
catch(err)
{
logger.error('producer.getStats error:', err.message);
continue;
}
for (const s of stats)
{
if (s.type !== 'inbound-rtp')
{
continue;
}
if (s.kind === 'video')
{
video_bitrates_in.push(s.bitrate);
video_scores_in.push(s.score);
}
else if (s.kind === 'audio')
{
audio_bitrates_in.push(s.bitrate);
audio_scores_in.push(s.score);
}
packets_counts_in.push(s.packetCount || 0);
packets_losts_in.push(s.packetsLost || 0);
packets_retransmitted_in.push(s.packetsRetransmitted || 0);
}
}
// iterate producers
for (const producer of transport._producers.values())
{
let stats = [];
// iterate consumers
for (const consumer of transport._consumers.values())
{
if (consumer.type === 'pipe')
{
continue;
}
let stats = [];
try
{
stats = await consumer.getStats();
}
catch(err)
{
logger.error('consumer.getStats error:', err.message);
continue;
}
for (const s of stats)
{
if(s.type !== 'outbound-rtp'){
continue;
}
if (s.kind === 'video')
{
video_bitrates_out.push(s.bitrate || 0);
spatial_layers_out.push(consumer.currentLayers ? consumer.currentLayers.spatialLayer : 0);
temporal_layers_out.push(consumer.currentLayers ? consumer.currentLayers.temporalLayer : 0);
}
else if(s.kind === 'audio')
{
audio_bitrates_out.push(s.bitrate || 0);
}
round_trip_times_out.push(s.roundTripTime || 0);
packets_counts_out.push(s.packetCount || 0);
packets_losts_out.push(s.packetsLost || 0);
}
}
}
}
}
}
catch(err)
{
logger.error('collectStats error:', err.message);
}
finally
{
Object.assign(mediasoupStats, {
workers_cpu: formatStats(workers_cpu),
workers_memory: formatStats(workers_memory),
rooms: formatStats(rooms),
peers: formatStats(peers),
// in
video_bitrates_in: formatStats(video_bitrates_in),
video_scores_in: formatStats(video_scores_in),
audio_bitrates_in: formatStats(audio_bitrates_in),
audio_scores_in: formatStats(audio_scores_in),
packets_counts_in: formatStats(packets_counts_in),
packets_losts_in: formatStats(packets_losts_in),
packets_retransmitted_in: formatStats(packets_retransmitted_in),
// out
video_bitrates_out: formatStats(video_bitrates_out),
audio_bitrates_out: formatStats(audio_bitrates_out),
round_trip_times_out: formatStats(round_trip_times_out),
packets_counts_out: formatStats(packets_counts_out),
packets_losts_out: formatStats(packets_losts_out),
spatial_layers_out: formatStats(spatial_layers_out),
temporal_layers_out: formatStats(temporal_layers_out),
});
}
try
{
stats = await producer.getStats();
}
catch (err)
{
logger.error('producer.getStats error:', err.message);
continue;
}
for (const s of stats)
{
if (s.type !== 'inbound-rtp')
{
continue;
}
if (s.kind === 'video')
{
videoBitratesIn.push(s.bitrate);
videoScoresIn.push(s.score);
}
else if (s.kind === 'audio')
{
audioBitratesIn.push(s.bitrate);
audioScoresIn.push(s.score);
}
packetsCountsIn.push(s.packetCount || 0);
packetsLostsIn.push(s.packetsLost || 0);
packetsRetransmittedIn.push(s.packetsRetransmitted || 0);
}
}
const end = process.hrtime(start);
logger.info(`collectStats (elapsed: ${end[0] * 1e3 + end[1] * 1e-6} ms)`);
}
// iterate consumers
for (const consumer of transport._consumers.values())
{
if (consumer.type === 'pipe')
{
continue;
}
let stats = [];
// mediasoup metrics
[
{ name: 'workers_count', statName: 'workers_cpu', statValue: 'length' },
{ name: 'workers_cpu', statName: 'workers_cpu', statValue: 'sum' },
{ name: 'workers_memory', statName: 'workers_memory', statValue: 'sum' },
//
{ name: 'rooms', statName: 'rooms', statValue: 'sum' },
{ name: 'peers', statName: 'peers', statValue: 'sum' },
// audio in
{ name: 'audio_in_count', statName: 'audio_bitrates_in', statValue: 'length' },
// audio in bitrates
{ name: 'audio_bitrates_in_sum', statName: 'audio_bitrates_in', statValue: 'sum' },
{ name: 'audio_bitrates_in_mean', statName: 'audio_bitrates_in', statValue: 'mean' },
{ name: 'audio_bitrates_in_min', statName: 'audio_bitrates_in', statValue: 'min' },
{ name: 'audio_bitrates_in_max', statName: 'audio_bitrates_in', statValue: 'max' },
{ name: 'audio_bitrates_in_p25', statName: 'audio_bitrates_in', statValue: 'p25' },
// audio in scores
{ name: 'audio_scores_in_mean', statName: 'audio_scores_in', statValue: 'mean' },
{ name: 'audio_scores_in_min', statName: 'audio_scores_in', statValue: 'min' },
{ name: 'audio_scores_in_max', statName: 'audio_scores_in', statValue: 'max' },
{ name: 'audio_scores_in_p25', statName: 'audio_scores_in', statValue: 'p25' },
// video in
{ name: 'video_in_count', statName: 'video_bitrates_in', statValue: 'length' },
// video in bitrates
{ name: 'video_bitrates_in_sum', statName: 'video_bitrates_in', statValue: 'sum' },
{ name: 'video_bitrates_in_mean', statName: 'video_bitrates_in', statValue: 'mean' },
{ name: 'video_bitrates_in_min', statName: 'video_bitrates_in', statValue: 'min' },
{ name: 'video_bitrates_in_max', statName: 'video_bitrates_in', statValue: 'max' },
{ name: 'video_bitrates_in_p25', statName: 'video_bitrates_in', statValue: 'p25' },
// video in scores
{ name: 'video_scores_in_mean', statName: 'video_scores_in', statValue: 'mean' },
{ name: 'video_scores_in_min', statName: 'video_scores_in', statValue: 'min' },
{ name: 'video_scores_in_max', statName: 'video_scores_in', statValue: 'max' },
{ name: 'video_scores_in_p25', statName: 'video_scores_in', statValue: 'p25' },
// packets in
{ name: 'packets_counts_in_sum', statName: 'packets_counts_in', statValue: 'sum' },
{ name: 'packets_counts_in_mean', statName: 'packets_counts_in', statValue: 'mean' },
{ name: 'packets_counts_in_min', statName: 'packets_counts_in', statValue: 'min' },
{ name: 'packets_counts_in_max', statName: 'packets_counts_in', statValue: 'max' },
{ name: 'packets_counts_in_p25', statName: 'packets_counts_in', statValue: 'p25' },
{ name: 'packets_losts_in_sum', statName: 'packets_losts_in', statValue: 'sum' },
{ name: 'packets_losts_in_mean', statName: 'packets_losts_in', statValue: 'mean' },
{ name: 'packets_losts_in_min', statName: 'packets_losts_in', statValue: 'min' },
{ name: 'packets_losts_in_max', statName: 'packets_losts_in', statValue: 'max' },
{ name: 'packets_losts_in_p25', statName: 'packets_losts_in', statValue: 'p25' },
{ name: 'packets_retransmitted_in_sum', statName: 'packets_retransmitted_in', statValue: 'sum' },
{ name: 'packets_retransmitted_in_mean', statName: 'packets_retransmitted_in', statValue: 'mean' },
{ name: 'packets_retransmitted_in_min', statName: 'packets_retransmitted_in', statValue: 'min' },
{ name: 'packets_retransmitted_in_max', statName: 'packets_retransmitted_in', statValue: 'max' },
{ name: 'packets_retransmitted_in_p25', statName: 'packets_retransmitted_in', statValue: 'p25' },
// audio out
{ name: 'audio_out_count', statName: 'audio_bitrates_out', statValue: 'length' },
{ name: 'audio_bitrates_out_sum', statName: 'audio_bitrates_out', statValue: 'sum' },
{ name: 'audio_bitrates_out_mean', statName: 'audio_bitrates_out', statValue: 'mean' },
{ name: 'audio_bitrates_out_min', statName: 'audio_bitrates_out', statValue: 'min' },
{ name: 'audio_bitrates_out_max', statName: 'audio_bitrates_out', statValue: 'max' },
{ name: 'audio_bitrates_out_p25', statName: 'audio_bitrates_out', statValue: 'p25' },
// video out
{ name: 'video_out_count', statName: 'video_bitrates_out', statValue: 'length' },
{ name: 'video_bitrates_out_sum', statName: 'video_bitrates_out', statValue: 'sum' },
{ name: 'video_bitrates_out_mean', statName: 'video_bitrates_out', statValue: 'mean' },
{ name: 'video_bitrates_out_min', statName: 'video_bitrates_out', statValue: 'min' },
{ name: 'video_bitrates_out_max', statName: 'video_bitrates_out', statValue: 'max' },
{ name: 'video_bitrates_out_p25', statName: 'video_bitrates_out', statValue: 'p25' },
// sl
{ name: 'spatial_layers_out_mean', statName: 'spatial_layers_out', statValue: 'mean' },
{ name: 'spatial_layers_out_min', statName: 'spatial_layers_out', statValue: 'min' },
{ name: 'spatial_layers_out_max', statName: 'spatial_layers_out', statValue: 'max' },
{ name: 'spatial_layers_out_p25', statName: 'spatial_layers_out', statValue: 'p25' },
// tl
{ name: 'temporal_layers_out_mean', statName: 'temporal_layers_out', statValue: 'mean' },
{ name: 'temporal_layers_out_min', statName: 'temporal_layers_out', statValue: 'min' },
{ name: 'temporal_layers_out_max', statName: 'temporal_layers_out', statValue: 'max' },
{ name: 'temporal_layers_out_p25', statName: 'temporal_layers_out', statValue: 'p25' },
// rtt out
{ name: 'round_trip_times_out_mean', statName: 'round_trip_times_out', statValue: 'mean' },
{ name: 'round_trip_times_out_min', statName: 'round_trip_times_out', statValue: 'min' },
{ name: 'round_trip_times_out_max', statName: 'round_trip_times_out', statValue: 'max' },
{ name: 'round_trip_times_out_p25', statName: 'round_trip_times_out', statValue: 'p25' },
try
{
stats = await consumer.getStats();
}
catch (err)
{
logger.error('consumer.getStats error:', err.message);
continue;
}
for (const s of stats)
{
if (s.type !== 'outbound-rtp')
{
continue;
}
if (s.kind === 'video')
{
videoBitratesOut.push(s.bitrate || 0);
spatialLayersOut.push(consumer.currentLayers
? consumer.currentLayers.spatialLayer : 0);
temporalLayersOut.push(consumer.currentLayers
? consumer.currentLayers.temporalLayer : 0);
}
else if (s.kind === 'audio')
{
audioBitratesOut.push(s.bitrate || 0);
}
roundTripTimesOut.push(s.roundTripTime || 0);
packetsCountsOut.push(s.packetCount || 0);
packetsLostsOut.push(s.packetsLost || 0);
}
}
}
}
}
}
catch (err)
{
logger.error('collectStats error:', err.message);
}
finally
{
Object.assign(mediasoupStats, {
workersCpu : formatStats(workersCpu),
workersMemory : formatStats(workersMemory),
rooms : formatStats(rooms),
peers : formatStats(peers),
// in
videoBitratesIn : formatStats(videoBitratesIn),
videoScoresIn : formatStats(videoScoresIn),
audioBitratesIn : formatStats(audioBitratesIn),
audioScoresIn : formatStats(audioScoresIn),
packetsCountsIn : formatStats(packetsCountsIn),
packetsLostsIn : formatStats(packetsLostsIn),
packetsRetransmittedIn : formatStats(packetsRetransmittedIn),
// out
videoBitratesOut : formatStats(videoBitratesOut),
audioBitratesOut : formatStats(audioBitratesOut),
roundTripTimesOut : formatStats(roundTripTimesOut),
packetsCountsOut : formatStats(packetsCountsOut),
packetsLostsOut : formatStats(packetsLostsOut),
spatialLayersOut : formatStats(spatialLayersOut),
temporalLayersOut : formatStats(temporalLayersOut)
});
}
].forEach(({ name, statName, statValue }) => {
new promClient.Gauge({
name: `mediasoup_${name}`,
help: `MediaSoup ${name}`,
labelNames: [],
registers: [ register ],
async collect()
{
await collectStats();
if (mediasoupStats[statName] !== undefined && mediasoupStats[statName][statValue] !== undefined)
{
this.set({}, mediasoupStats[statName][statValue]);
}
}
});
});
const end = process.hrtime(start);
return register;
logger.info(`collectStats (elapsed: ${(end[0] * 1e3) + (end[1] * 1e-6)} ms)`);
};
// mediasoup metrics
[
{ name: 'workers_count', statName: 'workersCpu', statValue: 'length' },
{ name: 'workers_cpu', statName: 'workersCpu', statValue: 'sum' },
{ name: 'workers_memory', statName: 'workersMemory', statValue: 'sum' },
//
{ name: 'rooms', statName: 'rooms', statValue: 'sum' },
{ name: 'peers', statName: 'peers', statValue: 'sum' },
// audio in
{ name: 'audio_in_count', statName: 'audioBitratesIn', statValue: 'length' },
// audio in bitrates
{ name: 'audio_bitrates_in_sum', statName: 'audioBitratesIn', statValue: 'sum' },
{ name: 'audio_bitrates_in_mean', statName: 'audioBitratesIn', statValue: 'mean' },
{ name: 'audio_bitrates_in_min', statName: 'audioBitratesIn', statValue: 'min' },
{ name: 'audio_bitrates_in_max', statName: 'audioBitratesIn', statValue: 'max' },
{ name: 'audio_bitrates_in_p25', statName: 'audioBitratesIn', statValue: 'p25' },
// audio in scores
{ name: 'audio_scores_in_mean', statName: 'audioScoresIn', statValue: 'mean' },
{ name: 'audio_scores_in_min', statName: 'audioScoresIn', statValue: 'min' },
{ name: 'audio_scores_in_max', statName: 'audioScoresIn', statValue: 'max' },
{ name: 'audio_scores_in_p25', statName: 'audioScoresIn', statValue: 'p25' },
// video in
{ name: 'video_in_count', statName: 'videoBitratesIn', statValue: 'length' },
// video in bitrates
{ name: 'video_bitrates_in_sum', statName: 'videoBitratesIn', statValue: 'sum' },
{ name: 'video_bitrates_in_mean', statName: 'videoBitratesIn', statValue: 'mean' },
{ name: 'video_bitrates_in_min', statName: 'videoBitratesIn', statValue: 'min' },
{ name: 'video_bitrates_in_max', statName: 'videoBitratesIn', statValue: 'max' },
{ name: 'video_bitrates_in_p25', statName: 'videoBitratesIn', statValue: 'p25' },
// video in scores
{ name: 'video_scores_in_mean', statName: 'videoScoresIn', statValue: 'mean' },
{ name: 'video_scores_in_min', statName: 'videoScoresIn', statValue: 'min' },
{ name: 'video_scores_in_max', statName: 'videoScoresIn', statValue: 'max' },
{ name: 'video_scores_in_p25', statName: 'videoScoresIn', statValue: 'p25' },
// packets in
{ name: 'packets_counts_in_sum', statName: 'packetsCountsIn', statValue: 'sum' },
{ name: 'packets_counts_in_mean', statName: 'packetsCountsIn', statValue: 'mean' },
{ name: 'packets_counts_in_min', statName: 'packetsCountsIn', statValue: 'min' },
{ name: 'packets_counts_in_max', statName: 'packetsCountsIn', statValue: 'max' },
{ name: 'packets_counts_in_p25', statName: 'packetsCountsIn', statValue: 'p25' },
{ name: 'packets_losts_in_sum', statName: 'packetsLostsIn', statValue: 'sum' },
{ name: 'packets_losts_in_mean', statName: 'packetsLostsIn', statValue: 'mean' },
{ name: 'packets_losts_in_min', statName: 'packetsLostsIn', statValue: 'min' },
{ name: 'packets_losts_in_max', statName: 'packetsLostsIn', statValue: 'max' },
{ name: 'packets_losts_in_p25', statName: 'packetsLostsIn', statValue: 'p25' },
{ name: 'packets_retransmitted_in_sum', statName: 'packetsRetransmittedIn', statValue: 'sum' },
{ name: 'packets_retransmitted_in_mean', statName: 'packetsRetransmittedIn', statValue: 'mean' },
{ name: 'packets_retransmitted_in_min', statName: 'packetsRetransmittedIn', statValue: 'min' },
{ name: 'packets_retransmitted_in_max', statName: 'packetsRetransmittedIn', statValue: 'max' },
{ name: 'packets_retransmitted_in_p25', statName: 'packetsRetransmittedIn', statValue: 'p25' },
// audio out
{ name: 'audio_out_count', statName: 'audioBitratesOut', statValue: 'length' },
{ name: 'audio_bitrates_out_sum', statName: 'audioBitratesOut', statValue: 'sum' },
{ name: 'audio_bitrates_out_mean', statName: 'audioBitratesOut', statValue: 'mean' },
{ name: 'audio_bitrates_out_min', statName: 'audioBitratesOut', statValue: 'min' },
{ name: 'audio_bitrates_out_max', statName: 'audioBitratesOut', statValue: 'max' },
{ name: 'audio_bitrates_out_p25', statName: 'audioBitratesOut', statValue: 'p25' },
// video out
{ name: 'video_out_count', statName: 'videoBitratesOut', statValue: 'length' },
{ name: 'video_bitrates_out_sum', statName: 'videoBitratesOut', statValue: 'sum' },
{ name: 'video_bitrates_out_mean', statName: 'videoBitratesOut', statValue: 'mean' },
{ name: 'video_bitrates_out_min', statName: 'videoBitratesOut', statValue: 'min' },
{ name: 'video_bitrates_out_max', statName: 'videoBitratesOut', statValue: 'max' },
{ name: 'video_bitrates_out_p25', statName: 'videoBitratesOut', statValue: 'p25' },
// sl
{ name: 'spatial_layers_out_mean', statName: 'spatialLayersOut', statValue: 'mean' },
{ name: 'spatial_layers_out_min', statName: 'spatialLayersOut', statValue: 'min' },
{ name: 'spatial_layers_out_max', statName: 'spatialLayersOut', statValue: 'max' },
{ name: 'spatial_layers_out_p25', statName: 'spatialLayersOut', statValue: 'p25' },
// tl
{ name: 'temporal_layers_out_mean', statName: 'temporalLayersOut', statValue: 'mean' },
{ name: 'temporal_layers_out_min', statName: 'temporalLayersOut', statValue: 'min' },
{ name: 'temporal_layers_out_max', statName: 'temporalLayersOut', statValue: 'max' },
{ name: 'temporal_layers_out_p25', statName: 'temporalLayersOut', statValue: 'p25' },
// rtt out
{ name: 'round_trip_times_out_mean', statName: 'roundTripTimesOut', statValue: 'mean' },
{ name: 'round_trip_times_out_min', statName: 'roundTripTimesOut', statValue: 'min' },
{ name: 'round_trip_times_out_max', statName: 'roundTripTimesOut', statValue: 'max' },
{ name: 'round_trip_times_out_p25', statName: 'roundTripTimesOut', statValue: 'p25' }
].forEach(({ name, statName, statValue }) =>
{
// eslint-disable-next-line no-new
new promClient.Gauge({
name : `mediasoup_${name}`,
help : `MediaSoup ${name}`,
labelNames : [],
registers : [ register ],
async collect()
{
await collectStats();
if (mediasoupStats[statName] !== undefined
&& mediasoupStats[statName][statValue] !== undefined)
{
this.set({}, mediasoupStats[statName][statValue]);
}
else
{
logger.warn(`${statName}.${statValue} not found`);
}
}
});
});
return register;
};

View file

@ -19,218 +19,218 @@ const metadata = {
module.exports = async function(workers, rooms, peers, registry, config)
{
const newMetrics = function(subsystem)
{
const namespace = 'mediasoup';
const metrics = new Map();
const newMetrics = function(subsystem)
{
const namespace = 'mediasoup';
const metrics = new Map();
for (const key in metadata)
{
if (Object.prototype.hasOwnProperty.call(metadata, key))
{
const value = metadata[key];
const name = key.split(/(?=[A-Z])/).join('_')
.toLowerCase();
const unit = value.unit;
const metricType = value.metricType;
let s = `${namespace}_${subsystem}_${name}`;
for (const key in metadata)
{
if (Object.prototype.hasOwnProperty.call(metadata, key))
{
const value = metadata[key];
const name = key.split(/(?=[A-Z])/).join('_')
.toLowerCase();
const unit = value.unit;
const metricType = value.metricType;
let s = `${namespace}_${subsystem}_${name}`;
if (unit)
{
s += `_${unit}`;
}
const m = new metricType({
name : s, help : `${subsystem}.${key}`, labelNames : labelNames, registers : [ registry ] });
if (unit)
{
s += `_${unit}`;
}
const m = new metricType({
name : s, help : `${subsystem}.${key}`, labelNames : labelNames, registers : [ registry ] });
metrics.set(key, m);
}
}
metrics.set(key, m);
}
}
return metrics;
};
return metrics;
};
const commonLabels = function(both, fn)
{
for (const roomId of rooms.keys())
{
for (const [ peerId, peer ] of peers)
{
if (fn(peer))
{
const displayName = peer._displayName;
const userAgent = peer._socket.client.request.headers['user-agent'];
const kind = both.kind;
const codec = both.rtpParameters.codecs[0].mimeType.split('/')[1];
const commonLabels = function(both, fn)
{
for (const roomId of rooms.keys())
{
for (const [ peerId, peer ] of peers)
{
if (fn(peer))
{
const displayName = peer._displayName;
const userAgent = peer._socket.client.request.headers['user-agent'];
const kind = both.kind;
const codec = both.rtpParameters.codecs[0].mimeType.split('/')[1];
return { roomId, peerId, displayName, userAgent, kind, codec };
}
}
}
throw new Error('cannot find common labels');
};
return { roomId, peerId, displayName, userAgent, kind, codec };
}
}
}
throw new Error('cannot find common labels');
};
const addr = async function(ip, port)
{
if (config.deidentify)
{
const a = ip.split('.');
const addr = async function(ip, port)
{
if (config.deidentify)
{
const a = ip.split('.');
for (let i = 0; i < a.length - 2; i++)
{
a[i] = 'xx';
}
for (let i = 0; i < a.length - 2; i++)
{
a[i] = 'xx';
}
return `${a.join('.')}:${port}`;
}
else if (config.numeric)
{
return `${ip}:${port}`;
}
else
{
try
{
const a = await resolver.reverse(ip);
return `${a.join('.')}:${port}`;
}
else if (config.numeric)
{
return `${ip}:${port}`;
}
else
{
try
{
const a = await resolver.reverse(ip);
ip = a[0];
}
catch (err)
{
logger.error(`reverse DNS query failed: ${ip} ${err.code}`);
}
ip = a[0];
}
catch (err)
{
logger.error(`reverse DNS query failed: ${ip} ${err.code}`);
}
return `${ip}:${port}`;
}
};
return `${ip}:${port}`;
}
};
const quiet = function(s)
{
return config.quiet ? '' : s;
};
const quiet = function(s)
{
return config.quiet ? '' : s;
};
const setValue = function(key, m, labels, v)
{
logger.debug(`setValue key=${key} v=${v}`);
switch (metadata[key].metricType)
{
case prom.Counter:
m.inc(labels, v);
break;
case prom.Gauge:
m.set(labels, v);
break;
default:
throw new Error(`unexpected metric: ${m}`);
}
};
const setValue = function(key, m, labels, v)
{
logger.debug(`setValue key=${key} v=${v}`);
switch (metadata[key].metricType)
{
case prom.Counter:
m.inc(labels, v);
break;
case prom.Gauge:
m.set(labels, v);
break;
default:
throw new Error(`unexpected metric: ${m}`);
}
};
logger.debug('collect');
const mRooms = new prom.Gauge({ name: 'edumeet_rooms', help: '#rooms', registers: [ registry ] });
logger.debug('collect');
const mRooms = new prom.Gauge({ name: 'edumeet_rooms', help: '#rooms', registers: [ registry ] });
mRooms.set(rooms.size);
const mPeers = new prom.Gauge({ name: 'edumeet_peers', help: '#peers', labelNames: [ 'room_id' ], registers: [ registry ] });
mRooms.set(rooms.size);
const mPeers = new prom.Gauge({ name: 'edumeet_peers', help: '#peers', labelNames: [ 'room_id' ], registers: [ registry ] });
for (const [ roomId, room ] of rooms)
{
mPeers.labels(roomId).set(Object.keys(room._peers).length);
}
for (const [ roomId, room ] of rooms)
{
mPeers.labels(roomId).set(Object.keys(room._peers).length);
}
const mConsumer = newMetrics('consumer');
const mProducer = newMetrics('producer');
const mConsumer = newMetrics('consumer');
const mProducer = newMetrics('producer');
for (const [ pid, worker ] of workers)
{
logger.debug(`visiting worker ${pid}`);
for (const router of worker._routers)
{
logger.debug(`visiting router ${router.id}`);
for (const [ transportId, transport ] of router._transports)
{
logger.debug(`visiting transport ${transportId}`);
const transportJson = await transport.dump();
for (const worker of workers)
{
logger.debug(`visiting worker ${worker.pid}`);
for (const router of worker._routers)
{
logger.debug(`visiting router ${router.id}`);
for (const [ transportId, transport ] of router._transports)
{
logger.debug(`visiting transport ${transportId}`);
const transportJson = await transport.dump();
if (transportJson.iceState != 'completed')
{
logger.debug(`skipping transport ${transportId}}: ${transportJson.iceState}`);
continue;
}
const iceSelectedTuple = transportJson.iceSelectedTuple;
const proto = iceSelectedTuple.protocol;
const localAddr = await addr(iceSelectedTuple.localIp,
iceSelectedTuple.localPort);
const remoteAddr = await addr(iceSelectedTuple.remoteIp,
iceSelectedTuple.remotePort);
if (transportJson.iceState != 'completed')
{
logger.debug(`skipping transport ${transportId}}: ${transportJson.iceState}`);
continue;
}
const iceSelectedTuple = transportJson.iceSelectedTuple;
const proto = iceSelectedTuple.protocol;
const localAddr = await addr(iceSelectedTuple.localIp,
iceSelectedTuple.localPort);
const remoteAddr = await addr(iceSelectedTuple.remoteIp,
iceSelectedTuple.remotePort);
for (const [ producerId, producer ] of transport._producers)
{
logger.debug(`visiting producer ${producerId}`);
const { roomId, peerId, displayName, userAgent, kind, codec } =
for (const [ producerId, producer ] of transport._producers)
{
logger.debug(`visiting producer ${producerId}`);
const { roomId, peerId, displayName, userAgent, kind, codec } =
commonLabels(producer, (peer) => peer._producers.has(producerId));
const a = await producer.getStats();
const a = await producer.getStats();
for (const x of a)
{
const type = x.type;
const labels = {
'pid' : pid,
'room_id' : roomId,
'peer_id' : peerId,
'display_name' : displayName,
'user_agent' : userAgent,
'transport_id' : quiet(transportId),
'proto' : proto,
'local_addr' : localAddr,
'remote_addr' : remoteAddr,
'id' : quiet(producerId),
'kind' : kind,
'codec' : codec,
'type' : type
};
for (const x of a)
{
const type = x.type;
const labels = {
'pid' : worker.pid,
'room_id' : roomId,
'peer_id' : peerId,
'display_name' : displayName,
'user_agent' : userAgent,
'transport_id' : quiet(transportId),
'proto' : proto,
'local_addr' : localAddr,
'remote_addr' : remoteAddr,
'id' : quiet(producerId),
'kind' : kind,
'codec' : codec,
'type' : type
};
for (const [ key, m ] of mProducer)
{
setValue(key, m, labels, x[key]);
}
}
}
for (const [ consumerId, consumer ] of transport._consumers)
{
logger.debug(`visiting consumer ${consumerId}`);
const { roomId, peerId, displayName, userAgent, kind, codec } =
for (const [ key, m ] of mProducer)
{
setValue(key, m, labels, x[key]);
}
}
}
for (const [ consumerId, consumer ] of transport._consumers)
{
logger.debug(`visiting consumer ${consumerId}`);
const { roomId, peerId, displayName, userAgent, kind, codec } =
commonLabels(consumer, (peer) => peer._consumers.has(consumerId));
const a = await consumer.getStats();
const a = await consumer.getStats();
for (const x of a)
{
if (x.type == 'inbound-rtp')
{
continue;
}
const type = x.type;
const labels =
{
'pid' : pid,
'room_id' : roomId,
'peer_id' : peerId,
'display_name' : displayName,
'user_agent' : userAgent,
'transport_id' : quiet(transportId),
'proto' : proto,
'local_addr' : localAddr,
'remote_addr' : remoteAddr,
'id' : quiet(consumerId),
'kind' : kind,
'codec' : codec,
'type' : type
};
for (const x of a)
{
if (x.type == 'inbound-rtp')
{
continue;
}
const type = x.type;
const labels =
{
'pid' : worker.pid,
'room_id' : roomId,
'peer_id' : peerId,
'display_name' : displayName,
'user_agent' : userAgent,
'transport_id' : quiet(transportId),
'proto' : proto,
'local_addr' : localAddr,
'remote_addr' : remoteAddr,
'id' : quiet(consumerId),
'kind' : kind,
'codec' : codec,
'type' : type
};
for (const [ key, m ] of mConsumer)
{
setValue(key, m, labels, x[key]);
}
}
}
}
}
}
for (const [ key, m ] of mConsumer)
{
setValue(key, m, labels, x[key]);
}
}
}
}
}
}
};

View file

@ -8,28 +8,18 @@ const collectDefaultMetrics = require('./metrics/default');
const RegisterAggregated = require('./metrics/aggregated');
const logger = new Logger('promClient');
const workers = new Map();
module.exports = async function(rooms, peers, config)
import { config } from './config';
module.exports = async function(workers, rooms, peers)
{
try
{
logger.debug(`config.deidentify=${config.deidentify}`);
logger.debug(`config.listen=${config.listen}`);
logger.debug(`config.numeric=${config.numeric}`);
logger.debug(`config.port=${config.port}`);
logger.debug(`config.quiet=${config.quiet}`);
mediasoup.observer.on('newworker', (worker) =>
{
logger.debug(`observing newworker ${worker.pid} #${workers.size}`);
workers.set(worker.pid, worker);
worker.observer.on('close', () =>
{
logger.debug(`observing close worker ${worker.pid} #${workers.size - 1}`);
workers.delete(worker.pid);
});
});
logger.debug(`config.prometheus.deidentify=${config.prometheus.deidentify}`);
logger.debug(`config.prometheus.listen=${config.prometheus.listen}`);
logger.debug(`config.prometheus.numeric=${config.prometheus.numeric}`);
logger.debug(`config.prometheus.port=${config.prometheus.port}`);
logger.debug(`config.prometheus.quiet=${config.prometheus.quiet}`);
const app = express();
@ -39,7 +29,7 @@ module.exports = async function(rooms, peers, config)
logger.debug(`GET ${req.originalUrl}`);
const registry = new promClient.Registry();
await collectDefaultMetrics(workers, rooms, peers, registry, config);
await collectDefaultMetrics(workers, rooms, peers, registry, config.prometheus);
res.set('Content-Type', registry.contentType);
const data = await registry.metrics();
@ -47,26 +37,26 @@ module.exports = async function(rooms, peers, config)
});
// aggregated register
const registerAggregated = RegisterAggregated(workers, rooms, peers, config);
const registerAggregated = RegisterAggregated(workers, rooms, peers, config.prometheus);
app.get('/metrics', async (req, res) =>
{
logger.debug(`GET ${req.originalUrl}`);
if (config.secret && req.headers.authorization !== 'Bearer ' + config.secret)
if (config.prometheus.secret && req.headers.authorization !== `Bearer ${ config.prometheus.secret}`)
{
logger.error(`Invalid authorization header`);
logger.error('Invalid authorization header');
return res.status(401).end();
}
res.set('Content-Type', registerAggregated.contentType);
const data = await registerAggregated.metrics();
res.end(data);
});
const server = app.listen(config.port || 8889,
config.listen || undefined, () =>
const server = app.listen(config.prometheus.port, config.prometheus.listen, () =>
{
const address = server.address();

View file

@ -146,12 +146,6 @@ async function run()
// Open the interactive server.
await interactiveServer(rooms, peers);
// start Prometheus exporter
if (config.prometheus)
{
await promExporter(rooms, peers, config.prometheus);
}
if (typeof (config.auth) === 'undefined')
{
logger.warn('Auth is not configured properly!');
@ -167,6 +161,12 @@ async function run()
// Run HTTPS server.
await runHttpsServer();
// start Prometheus exporter
if (config.prometheus.enabled)
{
await promExporter(mediasoupWorkers, rooms, peers);
}
// Run WebSocketServer.
await runWebSocketServer();