import * as child_process from 'child_process' ; import * as fs from 'fs' ; import * as http from 'http' ; import * as jsyaml from 'js-yaml' ; import * as mkdirp from 'mkdirp' ; import * as path from 'path' ; import * as logging from '../infrastructure/logging' ; export interface QueryResultData { resultType : 'matrix' | 'vector' | 'scalar' | 'string' ; result : Array < { metric : { [ labelValue ] : string ; } ; value : [ number , string ] ; } > ; } interface QueryResult { status : 'success' | 'error' ; data : QueryResultData ; errorType : string ; error : string ; } export class PrometheusClient { constructor ( private address ) { } query ( query ) { return new Promise < QueryResultData > ( ( fulfill , reject ) => { const url = `${ this . address }/api/v1/query?query=${ encodeURIComponent ( query ) }` ; http . get ( url , ( response ) => { if ( response . statusCode < 200 || response . statusCode > 299 ) { reject ( new Error ( `Got error ${ response . statusCode }` ) ) ; response . resume ( ) ; return ; } let body = '' ; response . on ( 'data' , ( data ) => { body += data ; } ) ; response . on ( 'end' , ( ) => { const result = JSON . parse ( body ) as QueryResult ; if ( result . status !== 'success' ) { return reject ( new Error ( `Error ${ result . errorType }: ${ result . error }` ) ) ; } fulfill ( result . data ) ; } ) ; } ) . on ( 'error' , ( e ) => { reject ( new Error ( `Failed to query prometheus API: ${ e }` ) ) ; } ) ; } ) ; } } export async function startPrometheus ( binaryFilename , configFilename , configJson , processArgs , endpoint ) { await writePrometheusConfigToDisk ( configFilename , configJson ) ; await spawnPrometheusSubprocess ( binaryFilename , processArgs , endpoint ) ; } async function writePrometheusConfigToDisk ( configFilename , configJson ) { await mkdirp . sync ( path . dirname ( configFilename ) ) ; const ymlTxt = jsyaml . safeDump ( configJson , { 'sortKeys' : true } ) ; await new Promise < void > ( ( resolve , reject ) => { fs . writeFile ( configFilename , ymlTxt , 'utf-8' , ( err ) => { if ( err ) { reject ( err ) ; } else { resolve ( ) ; } } ) ; } ) ; } async function spawnPrometheusSubprocess ( binaryFilename , processArgs , prometheusEndpoint ) { logging . info ( `Starting Prometheus with args [${ processArgs }]` ) ; const runProcess = child_process . spawn ( binaryFilename , processArgs ) ; runProcess . on ( 'error' , ( error ) => { logging . error ( `Error spawning Prometheus: ${ error }` ) ; } ) ; runProcess . on ( 'exit' , ( code , signal ) => { logging . error ( `Prometheus has exited with error. Code: ${ code }, Signal: ${ signal }` ) ; logging . error ( 'Restarting Prometheus...' ) ; spawnPrometheusSubprocess ( binaryFilename , processArgs , prometheusEndpoint ) ; } ) ; runProcess . stdout . pipe ( process . stdout ) ; runProcess . stderr . pipe ( process . stderr ) ; await waitForPrometheusReady ( `${ prometheusEndpoint }/api/v1/status/flags` ) ; logging . info ( 'Prometheus is ready!' ) ; return runProcess ; } async function waitForPrometheusReady ( prometheusEndpoint ) { while ( ! ( await isHttpEndpointHealthy ( prometheusEndpoint ) ) ) { await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ; } } function isHttpEndpointHealthy ( endpoint ) { return new Promise ( ( resolve , reject ) => { http . get ( endpoint , ( response ) => { resolve ( response . statusCode >= 200 && response . statusCode < 300 ) ; } ) . on ( 'error' , ( e ) => { resolve ( false ) ; } ) ; } ) ; }