mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-20 08:40:38 +00:00
metrics
This commit is contained in:
28
app.js
28
app.js
@@ -69,4 +69,32 @@ app.listen(PORT);
|
|||||||
|
|
||||||
logger.info(`listening for HTTP requests on port ${PORT}, serviceUrl is ${srf.locals.serviceUrl}`);
|
logger.info(`listening for HTTP requests on port ${PORT}, serviceUrl is ${srf.locals.serviceUrl}`);
|
||||||
|
|
||||||
|
const sessionTracker = require('./lib/session/session-tracker');
|
||||||
|
setInterval(() => {
|
||||||
|
srf.locals.stats.gauge('fs.sip.calls.count', sessionTracker.count);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
// report freeswitch stats periodically
|
||||||
|
const fsOpts = srf.locals.getFreeswitch();
|
||||||
|
const mrf = srf.locals.mrf;
|
||||||
|
|
||||||
|
async function pollFreeswitch(mrf) {
|
||||||
|
const stats = srf.locals.stats;
|
||||||
|
const ms = await mrf.connect(fsOpts);
|
||||||
|
logger.info({freeswitch: fsOpts}, 'connected to freeswitch for metrics monitoring');
|
||||||
|
setInterval(() => {
|
||||||
|
try {
|
||||||
|
stats.gauge('fs.media.channels.in_use', ms.currentSessions);
|
||||||
|
stats.gauge('fs.media.channels.free', ms.maxSessions - ms.currentSessions);
|
||||||
|
stats.gauge('fs.media.calls_per_second', ms.cps);
|
||||||
|
stats.gauge('fs.media.cpu_idle', ms.cpuIdle);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
logger.info(err, 'Error sending media server metrics');
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pollFreeswitch(mrf).catch((err) => logger.error(err, 'Error polling freeswitch'));
|
||||||
|
|
||||||
module.exports = {srf, logger};
|
module.exports = {srf, logger};
|
||||||
|
|||||||
@@ -172,6 +172,29 @@ class CallSession extends Emitter {
|
|||||||
sessionTracker.remove(this.callSid);
|
sessionTracker.remove(this.callSid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeUrl(url, method, auth) {
|
||||||
|
const hook = {
|
||||||
|
url,
|
||||||
|
method
|
||||||
|
};
|
||||||
|
if (auth && auth.username && auth.password) {
|
||||||
|
hook.auth = {
|
||||||
|
username: auth.username,
|
||||||
|
password: auth.password
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (typeof url === 'string' && url.startsWith('/')) {
|
||||||
|
const baseUrl = this.requestor.baseUrl;
|
||||||
|
hook.url = `${baseUrl}${url}`;
|
||||||
|
if (this.requestor.username && this.requestor.password) {
|
||||||
|
hook.auth = {
|
||||||
|
username: this.requestor.username,
|
||||||
|
password: this.requestor.password
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hook;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This is called when all tasks have completed. It is not implemented in the superclass
|
* This is called when all tasks have completed. It is not implemented in the superclass
|
||||||
* but provided as a convenience for subclasses that need to do cleanup at the end of
|
* but provided as a convenience for subclasses that need to do cleanup at the end of
|
||||||
@@ -536,7 +559,8 @@ class CallSession extends Emitter {
|
|||||||
|
|
||||||
// update calls db
|
// update calls db
|
||||||
this.logger.debug(`updating redis with ${JSON.stringify(this.callInfo)}`);
|
this.logger.debug(`updating redis with ${JSON.stringify(this.callInfo)}`);
|
||||||
this.updateCallStatus(this.callInfo, this.serviceUrl).catch((err) => this.logger.error(err, 'redis error'));
|
this.updateCallStatus(Object.assign({}, this.callInfo), this.serviceUrl)
|
||||||
|
.catch((err) => this.logger.error(err, 'redis error'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ class TaskDial extends Task {
|
|||||||
super(logger, opts);
|
super(logger, opts);
|
||||||
this.preconditions = TaskPreconditions.None;
|
this.preconditions = TaskPreconditions.None;
|
||||||
|
|
||||||
this.actionHook = this.data.actionHook;
|
|
||||||
this.earlyMedia = this.data.answerOnBridge === true;
|
this.earlyMedia = this.data.answerOnBridge === true;
|
||||||
this.callerId = this.data.callerId;
|
this.callerId = this.data.callerId;
|
||||||
this.dialMusic = this.data.dialMusic;
|
this.dialMusic = this.data.dialMusic;
|
||||||
@@ -128,7 +127,7 @@ class TaskDial extends Task {
|
|||||||
this._installDtmfDetection(cs, this.epOther, this.parentDtmfCollector);
|
this._installDtmfDetection(cs, this.epOther, this.parentDtmfCollector);
|
||||||
await this._attemptCalls(cs);
|
await this._attemptCalls(cs);
|
||||||
await this.awaitTaskDone();
|
await this.awaitTaskDone();
|
||||||
await this.performAction(Object.assign({}, cs.callInfo, this.results));
|
await this.performAction(this.results);
|
||||||
this._removeDtmfDetection(cs, this.epOther);
|
this._removeDtmfDetection(cs, this.epOther);
|
||||||
this._removeDtmfDetection(cs, this.ep);
|
this._removeDtmfDetection(cs, this.ep);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -280,6 +279,12 @@ class TaskDial extends Task {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('callStatusChange', (obj) => {
|
.on('callStatusChange', (obj) => {
|
||||||
|
if (this.results.dialCallStatus !== CallStatus.Completed) {
|
||||||
|
Object.assign(this.results, {
|
||||||
|
dialCallStatus: obj.callStatus,
|
||||||
|
dialCallSid: sd.callSid,
|
||||||
|
});
|
||||||
|
}
|
||||||
switch (obj.callStatus) {
|
switch (obj.callStatus) {
|
||||||
case CallStatus.Trying:
|
case CallStatus.Trying:
|
||||||
break;
|
break;
|
||||||
@@ -303,12 +308,6 @@ class TaskDial extends Task {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this.results.dialCallStatus !== CallStatus.Completed) {
|
|
||||||
Object.assign(this.results, {
|
|
||||||
dialCallStatus: obj.callStatus,
|
|
||||||
dialCallSid: sd.callSid,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.on('accept', () => {
|
.on('accept', () => {
|
||||||
this.logger.debug(`Dial:_attemptCalls - we have a winner: ${sd.callSid}`);
|
this.logger.debug(`Dial:_attemptCalls - we have a winner: ${sd.callSid}`);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class TaskListen extends Task {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.hook = this.normalizeUrl(this.url, 'GET', this.wsAuth);
|
this.hook = this.normalizeUrl(this.url, 'GET', this.wsAuth);
|
||||||
|
this.logger.debug({hook: this.hook}, 'prepared ws url');
|
||||||
if (this.playBeep) await this._playBeep(ep);
|
if (this.playBeep) await this._playBeep(ep);
|
||||||
if (this.transcribeTask) {
|
if (this.transcribeTask) {
|
||||||
this.logger.debug('TaskListen:exec - starting nested transcribe task');
|
this.logger.debug('TaskListen:exec - starting nested transcribe task');
|
||||||
@@ -91,12 +92,12 @@ class TaskListen extends Task {
|
|||||||
{sampleRate: this.sampleRate, mixType: this.mixType},
|
{sampleRate: this.sampleRate, mixType: this.mixType},
|
||||||
this.nested ? this.parentTask.sd.callInfo : cs.callInfo.toJSON(),
|
this.nested ? this.parentTask.sd.callInfo : cs.callInfo.toJSON(),
|
||||||
this.metadata);
|
this.metadata);
|
||||||
if (this.hook.username && this.hook.password) {
|
if (this.hook.auth) {
|
||||||
this.logger.debug({username: this.hook.username, password: this.hook.password},
|
this.logger.debug({username: this.hook.auth.username, password: this.hook.auth.password},
|
||||||
'TaskListen:_startListening basic auth');
|
'TaskListen:_startListening basic auth');
|
||||||
await this.ep.set({
|
await this.ep.set({
|
||||||
'MOD_AUDIO_BASIC_AUTH_USERNAME': this.hook.username,
|
'MOD_AUDIO_BASIC_AUTH_USERNAME': this.hook.auth.username,
|
||||||
'MOD_AUDIO_BASIC_AUTH_PASSWORD': this.hook.password
|
'MOD_AUDIO_BASIC_AUTH_PASSWORD': this.hook.auth.password
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await ep.forkAudioStart({
|
await ep.forkAudioStart({
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class TaskTag extends Task {
|
|||||||
async exec(cs) {
|
async exec(cs) {
|
||||||
super.exec(cs);
|
super.exec(cs);
|
||||||
cs.callInfo.customerData = this.data;
|
cs.callInfo.customerData = this.data;
|
||||||
this.logger.debug({customerData: this.data}, 'TaskTag:exec set customer data');
|
this.logger.debug({callInfo: cs.callInfo.toJSON()}, 'TaskTag:exec set customer data in callInfo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ const debug = require('debug')('jambonz:feature-server');
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const {TaskPreconditions} = require('../utils/constants');
|
const {TaskPreconditions} = require('../utils/constants');
|
||||||
const normalizeJamones = require('../utils/normalize-jamones');
|
const normalizeJamones = require('../utils/normalize-jamones');
|
||||||
const makeTask = require('./make_task');
|
|
||||||
const specs = new Map();
|
const specs = new Map();
|
||||||
const _specData = require('./specs');
|
const _specData = require('./specs');
|
||||||
for (const key in _specData) {specs.set(key, _specData[key]);}
|
for (const key in _specData) {specs.set(key, _specData[key]);}
|
||||||
@@ -86,9 +85,10 @@ class Task extends Emitter {
|
|||||||
|
|
||||||
async performAction(results, expectResponse = true) {
|
async performAction(results, expectResponse = true) {
|
||||||
if (this.actionHook) {
|
if (this.actionHook) {
|
||||||
const params = results ? Object.assign(results, this.cs.callInfo) : this.cs.callInfo;
|
const params = results ? Object.assign(results, this.cs.callInfo.toJSON()) : this.cs.callInfo.toJSON();
|
||||||
const json = await this.cs.requestor.request(this.actionHook, params);
|
const json = await this.cs.requestor.request(this.actionHook, params);
|
||||||
if (expectResponse && json && Array.isArray(json)) {
|
if (expectResponse && json && Array.isArray(json)) {
|
||||||
|
const makeTask = require('./make_task');
|
||||||
const tasks = normalizeJamones(this.logger, json).map((tdata) => makeTask(this.logger, tdata));
|
const tasks = normalizeJamones(this.logger, json).map((tdata) => makeTask(this.logger, tdata));
|
||||||
if (tasks && tasks.length > 0) {
|
if (tasks && tasks.length > 0) {
|
||||||
this.logger.info({tasks: tasks}, `${this.name} replacing application with ${tasks.length} tasks`);
|
this.logger.info({tasks: tasks}, `${this.name} replacing application with ${tasks.length} tasks`);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
const ip = require('ip');
|
const ip = require('ip');
|
||||||
const localIp = ip.address();
|
const localIp = ip.address();
|
||||||
const PORT = process.env.HTTP_PORT || 3000;
|
const PORT = process.env.HTTP_PORT || 3000;
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
function installSrfLocals(srf, logger) {
|
function installSrfLocals(srf, logger) {
|
||||||
if (srf.locals.dbHelpers) return;
|
assert(!srf.locals.dbHelpers);
|
||||||
|
|
||||||
const {getSBC, getSrf} = require('./sbc-pinger')(logger);
|
const {getSBC, getSrf} = require('./sbc-pinger')(logger);
|
||||||
|
|
||||||
const freeswitch = process.env.JAMBONES_FREESWITCH
|
const freeswitch = process.env.JAMBONES_FREESWITCH
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((fs) => {
|
.map((fs) => {
|
||||||
@@ -15,6 +15,9 @@ function installSrfLocals(srf, logger) {
|
|||||||
});
|
});
|
||||||
logger.info({freeswitch}, 'freeswitch inventory');
|
logger.info({freeswitch}, 'freeswitch inventory');
|
||||||
|
|
||||||
|
const StatsCollector = require('jambonz-stats-collector');
|
||||||
|
const stats = srf.locals.stats = new StatsCollector(logger);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
lookupAppByPhoneNumber,
|
lookupAppByPhoneNumber,
|
||||||
lookupAppBySid,
|
lookupAppBySid,
|
||||||
@@ -51,8 +54,11 @@ function installSrfLocals(srf, logger) {
|
|||||||
serviceUrl: `http://${localIp}:${PORT}`,
|
serviceUrl: `http://${localIp}:${PORT}`,
|
||||||
getSBC,
|
getSBC,
|
||||||
getSrf,
|
getSrf,
|
||||||
getFreeswitch: () => freeswitch[0]
|
getFreeswitch: () => freeswitch[0],
|
||||||
|
stats: stats
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logger.debug({locals: srf.locals}, 'srf.locals installed');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = installSrfLocals;
|
module.exports = installSrfLocals;
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class SingleDialer extends Emitter {
|
|||||||
status.sipStatus = err.status;
|
status.sipStatus = err.status;
|
||||||
if (err.status === 487) status.callStatus = CallStatus.NoAnswer;
|
if (err.status === 487) status.callStatus = CallStatus.NoAnswer;
|
||||||
else if ([486, 600].includes(err.status)) status.callStatus = CallStatus.Busy;
|
else if ([486, 600].includes(err.status)) status.callStatus = CallStatus.Busy;
|
||||||
this.logger.debug(`SingleDialer:exec outdial failure ${err.status}`);
|
this.logger.info(`SingleDialer:exec outdial failure ${err.status}`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.logger.error(err, 'SingleDialer:exec');
|
this.logger.error(err, 'SingleDialer:exec');
|
||||||
|
|||||||
@@ -31,15 +31,22 @@ class Requestor {
|
|||||||
|
|
||||||
const u = parseUrl(this.url);
|
const u = parseUrl(this.url);
|
||||||
const myPort = u.port ? `:${u.port}` : '';
|
const myPort = u.port ? `:${u.port}` : '';
|
||||||
const baseUrl = `${u.protocol}://${u.resource}${myPort}`;
|
const baseUrl = this._baseUrl = `${u.protocol}://${u.resource}${myPort}`;
|
||||||
|
|
||||||
this.get = bent(baseUrl, 'GET', 'buffer', 200, 201);
|
this.get = bent(baseUrl, 'GET', 'buffer', 200, 201);
|
||||||
this.post = bent(baseUrl, 'POST', 'buffer', 200, 201);
|
this.post = bent(baseUrl, 'POST', 'buffer', 200, 201);
|
||||||
|
|
||||||
|
this.username = hook.username;
|
||||||
|
this.password = hook.password;
|
||||||
|
|
||||||
assert(isAbsoluteUrl(this.url));
|
assert(isAbsoluteUrl(this.url));
|
||||||
assert(['GET', 'POST'].includes(this.method));
|
assert(['GET', 'POST'].includes(this.method));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get baseUrl() {
|
||||||
|
return this._baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an HTTP request.
|
* Make an HTTP request.
|
||||||
* All requests use json bodies.
|
* All requests use json bodies.
|
||||||
@@ -61,11 +68,11 @@ class Requestor {
|
|||||||
assert.ok, (['GET', 'POST'].includes(method), `Requestor:request method must be 'GET' or 'POST' not ${method}`);
|
assert.ok, (['GET', 'POST'].includes(method), `Requestor:request method must be 'GET' or 'POST' not ${method}`);
|
||||||
|
|
||||||
|
|
||||||
this.logger.debug({hook}, `Requestor:request ${method} ${url}`);
|
this.logger.debug({hook, params}, `Requestor:request ${method} ${url}`);
|
||||||
const buf = isRelativeUrl(url) ?
|
const buf = isRelativeUrl(url) ?
|
||||||
await this.post(url, params, this.authHeader) :
|
await this.post(url, params, this.authHeader) :
|
||||||
await bent(method, 'buffer', 200, 201)(url, params, basicAuth(username, password));
|
await bent(method, 'buffer', 200, 201)(url, params, basicAuth(username, password));
|
||||||
//this.logger.debug({body: }, `Requestor:request ${method} ${url} succeeded`);
|
this.logger.debug(`Requestor:request ${method} ${url} succeeded`);
|
||||||
|
|
||||||
if (buf && buf.toString().length > 0) {
|
if (buf && buf.toString().length > 0) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
1732
package-lock.json
generated
1732
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -30,25 +30,26 @@
|
|||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||||
"drachtio-fsmrf": "^1.5.14",
|
"drachtio-fsmrf": "^1.5.14",
|
||||||
"drachtio-srf": "^4.4.27",
|
"drachtio-srf": "^4.4.28",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"ip": "^1.1.5",
|
"ip": "^1.1.5",
|
||||||
"jambonz-db-helpers": "^0.3.2",
|
"jambonz-db-helpers": "^0.3.2",
|
||||||
"jambonz-realtimedb-helpers": "0.1.7",
|
"jambonz-realtimedb-helpers": "^0.1.7",
|
||||||
|
"jambonz-stats-collector": "^0.0.3",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"parse-url": "^5.0.1",
|
"parse-url": "^5.0.1",
|
||||||
"pino": "^5.14.0"
|
"pino": "^5.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"blue-tape": "^1.0.0",
|
"blue-tape": "^1.0.0",
|
||||||
"clear-module": "^4.0.0",
|
"clear-module": "^4.0.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"jsdoc": "^3.6.3",
|
"jsdoc": "^3.6.3",
|
||||||
"nyc": "^14.1.1",
|
"nyc": "^14.1.1",
|
||||||
"tap": "^14.10.2",
|
"tap": "^14.10.6",
|
||||||
"tap-dot": "^2.0.0",
|
"tap-dot": "^2.0.0",
|
||||||
"tap-spec": "^5.0.0",
|
"tap-spec": "^5.0.0",
|
||||||
"tape": "^4.11.0"
|
"tape": "^4.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user