@ -1,4 +1,4 @@
import { ANALYTICS_PATH , ZEROMQ_CONNECTION_STRING , ANLYTICS_PING_INTERVAL } from '../config'
import * as config from '../config' ;
const zmq = require ( 'zeromq' ) ;
const zmq = require ( 'zeromq' ) ;
@ -12,6 +12,10 @@ export class AnalyticsService {
private _requester : any ;
private _requester : any ;
private _ready : boolean = false ;
private _ready : boolean = false ;
private _pingResponded = false ;
private _pingResponded = false ;
private _zmqConnectionString = null ;
private _ipcPath = null ;
private _analyticsPinger : NodeJS.Timer = null ;
private _isClosed = false ;
constructor ( private _onResponse : ( response : any ) = > void ) {
constructor ( private _onResponse : ( response : any ) = > void ) {
@ -39,19 +43,41 @@ export class AnalyticsService {
}
}
public close() {
public close() {
// TODO: close socket & terminate process if you have any
this . _isClosed = true ;
console . log ( 'Terminating analytics service...' ) ;
clearInterval ( this . _analyticsPinger ) ;
if ( this . _ipcPath !== null ) {
fs . unlinkSync ( this . _ipcPath ) ;
}
this . _requester . close ( ) ;
this . _requester . close ( ) ;
console . log ( 'Ok' ) ;
}
}
public get ready ( ) : boolean { return this . _ready ; }
public get ready ( ) : boolean { return this . _ready ; }
private async _init() {
private async _init() {
this . _requester = zmq . socket ( 'pair' ) ;
this . _requester = zmq . socket ( 'pair' ) ;
let productionMode = process . env . NODE_ENV !== 'development' ;
if ( process . env . NODE_ENV !== 'development' ) {
this . _zmqConnectionString = ` tcp://127.0.0.1: ${ config . ZMQ_DEV_PORT } ` ; // debug mode
if ( productionMode ) {
this . _zmqConnectionString = config . ZMQ_CONNECTION_STRING ;
if ( this . _zmqConnectionString === null ) {
var createResult = await AnalyticsService . createIPCAddress ( ) ;
this . _zmqConnectionString = createResult . address ;
this . _ipcPath = createResult . file ;
}
}
console . log ( "Binding to zmq... %s" , this . _zmqConnectionString ) ;
this . _requester . connect ( this . _zmqConnectionString ) ;
this . _requester . on ( "message" , this . _onAnalyticsMessage . bind ( this ) ) ;
console . log ( 'Ok' ) ;
if ( productionMode ) {
console . log ( 'Creating analytics process...' ) ;
console . log ( 'Creating analytics process...' ) ;
try {
try {
var cp = await AnalyticsService . _runAnalyticsProcess ( ) ;
var cp = await AnalyticsService . _runAnalyticsProcess ( this . _zmqConnectionString ) ;
} catch ( error ) {
} catch ( error ) {
console . error ( 'Can`t run analytics process: %s' , error ) ;
console . error ( 'Can`t run analytics process: %s' , error ) ;
return ;
return ;
@ -59,11 +85,6 @@ export class AnalyticsService {
console . log ( 'Ok, pid: %s' , cp . pid ) ;
console . log ( 'Ok, pid: %s' , cp . pid ) ;
}
}
console . log ( "Binding to zmq...: %s" , ZEROMQ_CONNECTION_STRING ) ;
this . _requester . connect ( ZEROMQ_CONNECTION_STRING ) ;
this . _requester . on ( "message" , this . _onAnalyticsMessage . bind ( this ) ) ;
console . log ( 'Ok' ) ;
console . log ( 'Start analytics pinger...' ) ;
console . log ( 'Start analytics pinger...' ) ;
this . _runAlalyticsPinger ( ) ;
this . _runAlalyticsPinger ( ) ;
console . log ( 'Ok' ) ;
console . log ( 'Ok' ) ;
@ -71,21 +92,30 @@ export class AnalyticsService {
}
}
/ * *
/ * *
* Spawns analytics process . Reads process stderr and fails if it
* Spawns analytics process . Reads process stderr and fails if it isn ` t empty.
* is not empty . No need to stop process later .
* No need to stop the process later .
*
*
* @returns creaded child process
* @returns Creaded child process
* @throws Process start error or first exception during python start
* /
* /
private static async _runAnalyticsProcess ( ) : Promise < childProcess.ChildProcess > {
private static async _runAnalyticsProcess ( zmqConnectionString : string ) : Promise < childProcess.ChildProcess > {
let cp : childProcess.ChildProcess ;
let cp : childProcess.ChildProcess ;
if ( fs . existsSync ( path . join ( ANALYTICS_PATH , 'dist/worker/worker' ) ) ) {
let cpOptions = {
cwd : config.ANALYTICS_PATH ,
env : {
. . . process . env ,
ZMQ_CONNECTION_STRING : zmqConnectionString
}
} ;
if ( fs . existsSync ( path . join ( config . ANALYTICS_PATH , 'dist/worker/worker' ) ) ) {
console . log ( 'dist/worker/worker' ) ;
console . log ( 'dist/worker/worker' ) ;
cp = childProcess . spawn ( 'dist/worker/worker' , [ ] , { cwd : ANALYTICS_PATH } ) ;
cp = childProcess . spawn ( 'dist/worker/worker' , [ ] , cpOptions ) ;
} else {
} else {
console . log ( 'python3 server.py' ) ;
console . log ( 'python3 server.py' ) ;
// If compiled analytics script doesn't exist - fallback to regular python
// If compiled analytics script doesn't exist - fallback to regular python
console . log ( ANALYTICS_PATH ) ;
console . log ( config . ANALYTICS_PATH ) ;
cp = childProcess . spawn ( 'python3' , [ 'server.py' ] , { cwd : ANALYTICS_PATH } ) ;
cp = childProcess . spawn ( 'python3' , [ 'server.py' ] , cpOptions ) ;
}
}
if ( cp . pid === undefined ) {
if ( cp . pid === undefined ) {
@ -126,7 +156,7 @@ export class AnalyticsService {
private async _onAnalyticsDown() {
private async _onAnalyticsDown() {
console . log ( 'Analytics is down' ) ;
console . log ( 'Analytics is down' ) ;
if ( process . env . NODE_ENV !== 'development' ) {
if ( process . env . NODE_ENV !== 'development' ) {
await AnalyticsService . _runAnalyticsProcess ( ) ;
await AnalyticsService . _runAnalyticsProcess ( this . _zmqConnectionString ) ;
}
}
}
}
@ -152,7 +182,10 @@ export class AnalyticsService {
}
}
private async _runAlalyticsPinger() {
private async _runAlalyticsPinger() {
setInterval ( ( ) = > {
this . _analyticsPinger = setInterval ( ( ) = > {
if ( this . _isClosed ) {
return ;
}
if ( ! this . _pingResponded && this . _ready ) {
if ( ! this . _pingResponded && this . _ready ) {
this . _ready = false ;
this . _ready = false ;
this . _onAnalyticsDown ( ) ;
this . _onAnalyticsDown ( ) ;
@ -160,7 +193,14 @@ export class AnalyticsService {
this . _pingResponded = false ;
this . _pingResponded = false ;
// TODO: set life limit for this ping
// TODO: set life limit for this ping
this . sendMessage ( 'ping' ) ;
this . sendMessage ( 'ping' ) ;
} , ANLYTICS_PING_INTERVAL ) ;
} , config . ANLYTICS_PING_INTERVAL ) ;
}
private static async createIPCAddress ( ) : Promise < { address : string , file : string } > {
let filename = ` ${ process . pid } .ipc `
let p = path . join ( config . ZMQ_IPC_PATH , filename ) ;
fs . writeFileSync ( p , '' ) ;
return Promise . resolve ( { address : 'ipc://' + p , file : p } ) ;
}
}
}
}