2013-01-24 04:55:32 -08:00
var Prometheus = Prometheus || { } ;
2013-04-02 10:14:02 -07:00
var graphTemplate ;
2013-01-24 04:55:32 -08:00
2019-03-06 07:16:55 -08:00
var INPUT _DEBOUNCE _WAIT = 500 ; // ms
2013-04-17 07:49:21 -07:00
var SECOND = 1000 ;
2017-12-21 09:58:05 -08:00
/ * *
* Graph
* /
2016-09-03 12:05:23 -07:00
Prometheus . Graph = function ( element , options , handleChange , handleRemove ) {
2013-01-24 04:55:32 -08:00
this . el = element ;
2016-08-05 14:35:11 -07:00
this . graphHTML = null ;
2013-01-24 04:55:32 -08:00
this . options = options ;
2016-09-03 12:05:23 -07:00
this . handleChange = handleChange ;
this . handleRemove = function ( ) {
handleRemove ( this ) ;
} ;
2013-01-24 04:55:32 -08:00
this . rickshawGraph = null ;
this . data = [ ] ;
this . initialize ( ) ;
} ;
Prometheus . Graph . timeFactors = {
"y" : 60 * 60 * 24 * 365 ,
"w" : 60 * 60 * 24 * 7 ,
"d" : 60 * 60 * 24 ,
"h" : 60 * 60 ,
"m" : 60 ,
"s" : 1
} ;
Prometheus . Graph . stepValues = [
"1s" , "10s" , "1m" , "5m" , "15m" , "30m" , "1h" , "2h" , "6h" , "12h" , "1d" , "2d" ,
"1w" , "2w" , "4w" , "8w" , "1y" , "2y"
] ;
Prometheus . Graph . numGraphs = 0 ;
Prometheus . Graph . prototype . initialize = function ( ) {
var self = this ;
self . id = Prometheus . Graph . numGraphs ++ ;
// Set default options.
2015-11-11 11:04:37 -08:00
self . options . id = self . id ;
self . options . range _input = self . options . range _input || "1h" ;
if ( self . options . tab === undefined ) {
self . options . tab = 1 ;
2014-12-26 08:40:06 -08:00
}
2013-01-24 04:55:32 -08:00
// Draw graph controls and container from Handlebars template.
2013-04-02 10:14:02 -07:00
2017-03-03 12:36:06 -08:00
var options = {
'pathPrefix' : PATH _PREFIX ,
'buildVersion' : BUILD _VERSION
} ;
2016-11-16 17:29:58 -08:00
jQuery . extend ( options , self . options ) ;
self . graphHTML = $ ( Mustache . render ( graphTemplate , options ) ) ;
2016-08-05 14:35:11 -07:00
self . el . append ( self . graphHTML ) ;
2013-01-24 04:55:32 -08:00
// Get references to all the interesting elements in the graph container and
// bind event handlers.
var graphWrapper = self . el . find ( "#graph_wrapper" + self . id ) ;
self . queryForm = graphWrapper . find ( ".query_form" ) ;
2015-06-12 07:46:22 -07:00
2019-02-11 15:22:05 -08:00
// Auto-resize the text area on input or mouseclick
var resizeTextarea = function ( el ) {
var offset = el . offsetHeight - el . clientHeight ;
$ ( el ) . css ( 'height' , 'auto' ) . css ( 'height' , el . scrollHeight + offset ) ;
} ;
2015-06-12 07:46:22 -07:00
self . expr = graphWrapper . find ( "textarea[name=expr]" ) ;
self . expr . keypress ( function ( e ) {
2017-12-21 09:58:05 -08:00
const enter = 13 ;
if ( e . which == enter && ! e . shiftKey ) {
2015-06-12 07:46:22 -07:00
self . queryForm . submit ( ) ;
e . preventDefault ( ) ;
}
2019-03-06 07:16:55 -08:00
$ ( this ) . on ( 'keyup input' , debounce ( function ( ) {
resizeTextarea ( this ) ;
} , INPUT _DEBOUNCE _WAIT ) ) ;
2015-06-12 07:46:22 -07:00
} ) ;
2019-02-11 15:22:05 -08:00
self . expr . click ( function ( e ) {
resizeTextarea ( this ) ;
} ) ;
2016-09-03 12:05:23 -07:00
self . expr . change ( self . handleChange ) ;
2015-06-12 07:46:22 -07:00
2013-01-24 04:55:32 -08:00
self . rangeInput = self . queryForm . find ( "input[name=range_input]" ) ;
2015-01-14 08:56:03 -08:00
self . stackedBtn = self . queryForm . find ( ".stacked_btn" ) ;
2013-01-24 04:55:32 -08:00
self . stacked = self . queryForm . find ( "input[name=stacked]" ) ;
2016-01-11 05:53:32 -08:00
self . insertMetric = self . queryForm . find ( "select[name=insert_metric]" ) ;
2013-04-17 07:49:21 -07:00
self . refreshInterval = self . queryForm . find ( "select[name=refresh]" ) ;
2013-05-21 07:54:33 -07:00
2013-04-26 07:50:59 -07:00
self . consoleTab = graphWrapper . find ( ".console" ) ;
self . graphTab = graphWrapper . find ( ".graph_container" ) ;
2015-01-14 08:56:03 -08:00
self . tabs = graphWrapper . find ( "a[data-toggle='tab']" ) ;
2015-11-11 11:04:37 -08:00
self . tabs . eq ( self . options . tab ) . tab ( "show" ) ;
2015-01-14 08:56:03 -08:00
self . tabs . on ( "shown.bs.tab" , function ( e ) {
var target = $ ( e . target ) ;
2015-11-11 11:04:37 -08:00
self . options . tab = target . parent ( ) . index ( ) ;
2016-09-03 12:05:23 -07:00
self . handleChange ( ) ;
2015-01-14 08:56:03 -08:00
if ( $ ( "#" + target . attr ( "aria-controls" ) ) . hasClass ( "reload" ) ) {
self . submitQuery ( ) ;
2013-04-26 07:50:59 -07:00
}
} ) ;
2016-08-05 14:35:11 -07:00
2016-01-11 05:53:32 -08:00
// Return moves focus back to expr instead of submitting.
self . insertMetric . bind ( "keydown" , "return" , function ( e ) {
self . expr . focus ( ) ;
self . expr . val ( self . expr . val ( ) ) ;
return e . preventDefault ( ) ;
} )
2013-01-24 04:55:32 -08:00
2015-01-14 08:56:03 -08:00
self . error = graphWrapper . find ( ".error" ) . hide ( ) ;
2019-01-31 09:03:50 -08:00
self . warning = graphWrapper . find ( ".warning" ) . hide ( ) ;
2015-02-06 07:20:46 -08:00
self . graphArea = graphWrapper . find ( ".graph_area" ) ;
self . graph = self . graphArea . find ( ".graph" ) ;
self . yAxis = self . graphArea . find ( ".y_axis" ) ;
2013-01-24 04:55:32 -08:00
self . legend = graphWrapper . find ( ".legend" ) ;
self . spinner = graphWrapper . find ( ".spinner" ) ;
self . evalStats = graphWrapper . find ( ".eval_stats" ) ;
2013-03-21 09:06:37 -07:00
self . endDate = graphWrapper . find ( "input[name=end_input]" ) ;
2015-01-12 05:18:28 -08:00
self . endDate . datetimepicker ( {
2016-11-16 17:20:36 -08:00
locale : 'en' ,
format : 'YYYY-MM-DD HH:mm' ,
toolbarPlacement : 'bottom' ,
sideBySide : true ,
showTodayButton : true ,
showClear : true ,
showClose : true ,
2017-09-15 19:38:29 -07:00
timeZone : 'UTC' ,
2015-01-12 05:18:28 -08:00
} ) ;
2015-11-11 11:04:37 -08:00
if ( self . options . end _input ) {
2016-11-16 17:20:36 -08:00
self . endDate . data ( 'DateTimePicker' ) . date ( self . options . end _input ) ;
2013-03-21 09:06:37 -07:00
}
2016-11-16 17:20:36 -08:00
self . endDate . on ( "dp.change" , function ( ) { self . submitQuery ( ) ; } ) ;
2015-11-11 11:04:37 -08:00
self . refreshInterval . change ( function ( ) { self . updateRefresh ( ) ; } ) ;
2013-03-21 06:26:13 -07:00
2015-01-14 08:56:03 -08:00
self . isStacked = function ( ) {
return self . stacked . val ( ) === '1' ;
} ;
2018-10-26 05:16:40 -07:00
self . moment = graphWrapper . find ( "input[name=moment_input]" ) ;
self . moment . datetimepicker ( {
locale : 'en' ,
format : 'YYYY-MM-DD HH:mm:ss' ,
toolbarPlacement : 'bottom' ,
sideBySide : true ,
showTodayButton : true ,
showClear : true ,
showClose : true ,
timeZone : 'UTC' ,
} ) ;
if ( self . options . timestamp ) {
var date = new Date ( self . options . timestamp * 1000 )
self . moment . data ( 'DateTimePicker' ) . date ( date ) ;
} else if ( self . options . moment _input ) {
self . moment . data ( 'DateTimePicker' ) . date ( self . options . moment _input ) ;
2019-01-31 09:03:50 -08:00
}
2018-10-26 05:16:40 -07:00
self . moment . on ( "dp.change" , function ( ) { self . submitQuery ( ) ; } ) ;
2015-01-14 08:56:03 -08:00
var styleStackBtn = function ( ) {
var icon = self . stackedBtn . find ( '.glyphicon' ) ;
if ( self . isStacked ( ) ) {
self . stackedBtn . addClass ( "btn-primary" ) ;
icon . addClass ( "glyphicon-check" ) ;
icon . removeClass ( "glyphicon-unchecked" ) ;
} else {
self . stackedBtn . removeClass ( "btn-primary" ) ;
icon . addClass ( "glyphicon-unchecked" ) ;
icon . removeClass ( "glyphicon-check" ) ;
}
} ;
styleStackBtn ( ) ;
self . stackedBtn . click ( function ( ) {
2015-02-06 07:20:46 -08:00
if ( self . isStacked ( ) && self . graphJSON ) {
// If the graph was stacked, the original series data got mutated
// (scaled) and we need to reconstruct it from the original JSON data.
self . data = self . transformData ( self . graphJSON ) ;
}
2015-01-14 08:56:03 -08:00
self . stacked . val ( self . isStacked ( ) ? '0' : '1' ) ;
styleStackBtn ( ) ;
self . updateGraph ( ) ;
} ) ;
2013-04-26 07:50:59 -07:00
self . queryForm . submit ( function ( ) {
2013-07-23 17:21:31 -07:00
self . consoleTab . addClass ( "reload" ) ;
self . graphTab . addClass ( "reload" ) ;
2018-02-14 22:23:12 -08:00
self . handleChange ( ) ;
2013-04-26 07:50:59 -07:00
self . submitQuery ( ) ;
return false ;
} ) ;
2013-01-24 04:55:32 -08:00
self . spinner . hide ( ) ;
2013-07-13 10:30:51 -07:00
self . queryForm . find ( "button[name=inc_range]" ) . click ( function ( ) { self . increaseRange ( ) ; } ) ;
self . queryForm . find ( "button[name=dec_range]" ) . click ( function ( ) { self . decreaseRange ( ) ; } ) ;
2013-03-21 06:26:13 -07:00
2013-07-13 10:30:51 -07:00
self . queryForm . find ( "button[name=inc_end]" ) . click ( function ( ) { self . increaseEnd ( ) ; } ) ;
self . queryForm . find ( "button[name=dec_end]" ) . click ( function ( ) { self . decreaseEnd ( ) ; } ) ;
2013-03-21 06:26:13 -07:00
2018-10-26 05:16:40 -07:00
self . queryForm . find ( "button[name=inc_moment]" ) . click ( function ( ) { self . increaseMoment ( ) ; } ) ;
self . queryForm . find ( "button[name=dec_moment]" ) . click ( function ( ) { self . decreaseMoment ( ) ; } ) ;
2016-01-11 05:53:32 -08:00
self . insertMetric . change ( function ( ) {
self . expr . selection ( "replace" , { text : self . insertMetric . val ( ) , mode : "before" } ) ;
self . expr . focus ( ) ; // refocusing
} ) ;
2016-07-31 07:30:23 -07:00
var removeBtn = graphWrapper . find ( "[name=remove]" ) ;
removeBtn . click ( function ( ) {
self . remove ( ) ;
return false ;
} ) ;
2017-10-11 06:11:04 -07:00
self . checkTimeDrift ( ) ;
2017-12-21 09:58:05 -08:00
// initialize query history
if ( ! localStorage . getItem ( "history" ) ) {
localStorage . setItem ( "history" , JSON . stringify ( [ ] ) ) ;
}
2016-01-11 05:53:32 -08:00
self . populateInsertableMetrics ( ) ;
2013-02-06 08:08:05 -08:00
2013-01-24 04:55:32 -08:00
if ( self . expr . val ( ) ) {
self . submitQuery ( ) ;
}
} ;
2017-10-11 06:11:04 -07:00
Prometheus . Graph . prototype . checkTimeDrift = function ( ) {
var self = this ;
var browserTime = new Date ( ) . getTime ( ) / 1000 ;
$ . ajax ( {
method : "GET" ,
url : PATH _PREFIX + "/api/v1/query?query=time()" ,
dataType : "json" ,
2020-02-20 02:36:22 -08:00
success : function ( json , textStatus ) {
2017-10-11 06:11:04 -07:00
if ( json . status !== "success" ) {
self . showError ( "Error querying time." ) ;
return ;
}
var serverTime = json . data . result [ 0 ] ;
var diff = Math . abs ( browserTime - serverTime ) ;
if ( diff >= 30 ) {
2019-08-05 11:03:12 -07:00
self . showWarning (
2017-10-11 06:11:04 -07:00
"<div class=\"alert alert-warning\"><strong>Warning!</strong> Detected " +
diff . toFixed ( 2 ) +
" seconds time difference between your browser and the server. Prometheus relies on accurate time and time drift might cause unexpected query results.</div>"
) ;
}
} ,
error : function ( ) {
self . showError ( "Error loading time." ) ;
}
} ) ;
} ;
2016-01-11 05:53:32 -08:00
Prometheus . Graph . prototype . populateInsertableMetrics = function ( ) {
2013-02-06 08:08:05 -08:00
var self = this ;
$ . ajax ( {
method : "GET" ,
2015-06-25 03:42:18 -07:00
url : PATH _PREFIX + "/api/v1/label/__name__/values" ,
2013-02-06 08:08:05 -08:00
dataType : "json" ,
success : function ( json , textStatus ) {
2015-06-25 03:42:18 -07:00
if ( json . status !== "success" ) {
2015-11-11 11:04:37 -08:00
self . showError ( "Error loading available metrics!" ) ;
2015-06-25 03:42:18 -07:00
return ;
2016-08-05 14:35:11 -07:00
}
2017-12-21 09:58:05 -08:00
2019-10-08 18:52:12 -07:00
pageConfig . allMetrics = json . data || [ ] ; // todo: do we need self.allMetrics? Or can it just live on the page
2017-12-21 09:58:05 -08:00
for ( var i = 0 ; i < pageConfig . allMetrics . length ; i ++ ) {
self . insertMetric [ 0 ] . options . add ( new Option ( pageConfig . allMetrics [ i ] , pageConfig . allMetrics [ i ] ) ) ;
2015-06-25 03:42:18 -07:00
}
2015-07-20 11:24:08 -07:00
2016-10-17 00:37:11 -07:00
self . fuzzyResult = {
query : null ,
result : null ,
map : { }
}
2017-12-21 09:58:05 -08:00
self . initTypeahead ( self ) ;
2013-02-06 08:08:05 -08:00
} ,
error : function ( ) {
2014-10-30 08:18:05 -07:00
self . showError ( "Error loading available metrics!" ) ;
2013-02-06 08:08:05 -08:00
} ,
} ) ;
} ;
2017-12-21 09:58:05 -08:00
Prometheus . Graph . prototype . initTypeahead = function ( self ) {
2018-02-02 10:16:29 -08:00
const source = queryHistory . isEnabled ( ) ? pageConfig . queryHistMetrics . concat ( pageConfig . allMetrics ) : pageConfig . allMetrics ;
2017-12-21 09:58:05 -08:00
self . expr . typeahead ( {
2018-02-02 10:16:29 -08:00
autoSelect : false ,
2018-05-07 05:26:38 -07:00
source : source ,
2019-01-31 09:03:50 -08:00
items : 1000 ,
2017-12-21 09:58:05 -08:00
matcher : function ( item ) {
// If we have result for current query, skip
if ( self . fuzzyResult . query !== this . query ) {
self . fuzzyResult . query = this . query ;
self . fuzzyResult . map = { } ;
self . fuzzyResult . result = fuzzy . filter ( this . query . replace ( / /g , "" ) , this . source , {
pre : "<strong>" ,
post : "</strong>"
} ) ;
self . fuzzyResult . result . forEach ( function ( r ) {
self . fuzzyResult . map [ r . original ] = r ;
} ) ;
}
return item in self . fuzzyResult . map ;
} ,
sorter : function ( items ) {
items . sort ( function ( a , b ) {
var i = self . fuzzyResult . map [ b ] . score - self . fuzzyResult . map [ a ] . score ;
return i === 0 ? a . localeCompare ( b ) : i ;
} ) ;
return items ;
2019-02-01 00:54:12 -08:00
}
2017-12-21 09:58:05 -08:00
} ) ;
// This needs to happen after attaching the typeahead plugin, as it
// otherwise breaks the typeahead functionality.
self . expr . focus ( ) ;
2018-02-14 22:23:12 -08:00
queryHistory . bindHistoryEvents ( self ) ;
2017-12-21 09:58:05 -08:00
}
2013-02-06 08:08:05 -08:00
Prometheus . Graph . prototype . getOptions = function ( ) {
2013-01-24 04:55:32 -08:00
var self = this ;
var options = { } ;
var optionInputs = [
"range_input" ,
2013-03-21 09:06:37 -07:00
"end_input" ,
2013-01-24 04:55:32 -08:00
"step_input" ,
2018-10-26 05:16:40 -07:00
"stacked" ,
"moment_input"
2013-01-24 04:55:32 -08:00
] ;
self . queryForm . find ( "input" ) . each ( function ( index , element ) {
2015-11-11 11:04:37 -08:00
var name = element . name ;
if ( $ . inArray ( name , optionInputs ) >= 0 ) {
2016-07-25 11:58:21 -07:00
if ( element . value . length > 0 ) {
options [ name ] = element . value ;
}
2015-11-11 11:04:37 -08:00
}
2013-01-24 04:55:32 -08:00
} ) ;
2015-11-11 11:04:37 -08:00
options . expr = self . expr . val ( ) ;
options . tab = self . options . tab ;
2013-01-24 04:55:32 -08:00
return options ;
} ;
2013-04-17 07:49:21 -07:00
Prometheus . Graph . prototype . parseDuration = function ( rangeText ) {
2013-01-24 04:55:32 -08:00
var rangeRE = new RegExp ( "^([0-9]+)([ywdhms]+)$" ) ;
var matches = rangeText . match ( rangeRE ) ;
2015-11-11 11:04:37 -08:00
if ( ! matches ) { return ; }
2013-01-24 04:55:32 -08:00
if ( matches . length != 3 ) {
return 60 ;
}
var value = parseInt ( matches [ 1 ] ) ;
var unit = matches [ 2 ] ;
return value * Prometheus . Graph . timeFactors [ unit ] ;
} ;
Prometheus . Graph . prototype . increaseRange = function ( ) {
var self = this ;
2013-04-17 07:49:21 -07:00
var rangeSeconds = self . parseDuration ( self . rangeInput . val ( ) ) ;
2013-01-24 04:55:32 -08:00
for ( var i = 0 ; i < Prometheus . Graph . stepValues . length ; i ++ ) {
2013-04-17 07:49:21 -07:00
if ( rangeSeconds < self . parseDuration ( Prometheus . Graph . stepValues [ i ] ) ) {
2013-01-24 04:55:32 -08:00
self . rangeInput . val ( Prometheus . Graph . stepValues [ i ] ) ;
if ( self . expr . val ( ) ) {
self . submitQuery ( ) ;
}
return ;
}
}
} ;
Prometheus . Graph . prototype . decreaseRange = function ( ) {
var self = this ;
2013-04-17 07:49:21 -07:00
var rangeSeconds = self . parseDuration ( self . rangeInput . val ( ) ) ;
2013-01-24 04:55:32 -08:00
for ( var i = Prometheus . Graph . stepValues . length - 1 ; i >= 0 ; i -- ) {
2013-04-17 07:49:21 -07:00
if ( rangeSeconds > self . parseDuration ( Prometheus . Graph . stepValues [ i ] ) ) {
2013-01-24 04:55:32 -08:00
self . rangeInput . val ( Prometheus . Graph . stepValues [ i ] ) ;
if ( self . expr . val ( ) ) {
self . submitQuery ( ) ;
}
return ;
}
}
} ;
2013-03-21 06:26:13 -07:00
Prometheus . Graph . prototype . getEndDate = function ( ) {
2013-03-21 09:54:03 -07:00
var self = this ;
2013-03-21 06:26:13 -07:00
if ( ! self . endDate || ! self . endDate . val ( ) ) {
2016-11-16 17:20:36 -08:00
return moment ( ) ;
2013-03-21 06:26:13 -07:00
}
2016-11-16 17:20:36 -08:00
return self . endDate . data ( 'DateTimePicker' ) . date ( ) ;
2013-03-21 06:26:13 -07:00
} ;
Prometheus . Graph . prototype . getOrSetEndDate = function ( ) {
2013-03-21 09:54:03 -07:00
var self = this ;
2013-03-21 06:26:13 -07:00
var date = self . getEndDate ( ) ;
2013-03-21 09:54:03 -07:00
self . setEndDate ( date ) ;
return date ;
2015-11-11 11:04:37 -08:00
} ;
2013-03-21 06:26:13 -07:00
Prometheus . Graph . prototype . setEndDate = function ( date ) {
var self = this ;
2016-11-16 17:20:36 -08:00
self . endDate . data ( 'DateTimePicker' ) . date ( date ) ;
2013-03-21 06:26:13 -07:00
} ;
Prometheus . Graph . prototype . increaseEnd = function ( ) {
var self = this ;
2016-11-16 17:20:36 -08:00
var newDate = moment ( self . getOrSetEndDate ( ) ) ;
newDate . add ( self . parseDuration ( self . rangeInput . val ( ) ) / 2 , 'seconds' ) ;
self . setEndDate ( newDate ) ;
2013-03-21 09:44:21 -07:00
self . submitQuery ( ) ;
2013-03-21 06:26:13 -07:00
} ;
Prometheus . Graph . prototype . decreaseEnd = function ( ) {
var self = this ;
2016-11-16 17:20:36 -08:00
var newDate = moment ( self . getOrSetEndDate ( ) ) ;
newDate . subtract ( self . parseDuration ( self . rangeInput . val ( ) ) / 2 , 'seconds' ) ;
self . setEndDate ( newDate ) ;
2013-03-21 09:44:21 -07:00
self . submitQuery ( ) ;
2013-03-21 06:26:13 -07:00
} ;
2018-10-26 05:16:40 -07:00
Prometheus . Graph . prototype . getMoment = function ( ) {
var self = this ;
if ( ! self . moment || ! self . moment . val ( ) ) {
return moment ( ) ;
}
return self . moment . data ( 'DateTimePicker' ) . date ( ) ;
} ;
Prometheus . Graph . prototype . getOrSetMoment = function ( ) {
var self = this ;
var date = self . getMoment ( ) ;
self . setMoment ( date ) ;
return date ;
} ;
Prometheus . Graph . prototype . setMoment = function ( date ) {
var self = this ;
self . moment . data ( 'DateTimePicker' ) . date ( date ) ;
} ;
Prometheus . Graph . prototype . increaseMoment = function ( ) {
var self = this ;
var newDate = moment ( self . getOrSetMoment ( ) ) ;
newDate . add ( 10 , 'seconds' ) ;
self . setMoment ( newDate ) ;
self . submitQuery ( ) ;
} ;
Prometheus . Graph . prototype . decreaseMoment = function ( ) {
var self = this ;
var newDate = moment ( self . getOrSetMoment ( ) ) ;
newDate . subtract ( 10 , 'seconds' ) ;
self . setMoment ( newDate ) ;
self . submitQuery ( ) ;
} ;
2013-01-24 04:55:32 -08:00
Prometheus . Graph . prototype . submitQuery = function ( ) {
var self = this ;
2014-10-30 08:18:05 -07:00
self . clearError ( ) ;
2019-01-31 09:03:50 -08:00
self . clearWarning ( ) ;
2013-05-02 07:55:47 -07:00
if ( ! self . expr . val ( ) ) {
2013-07-23 17:21:31 -07:00
return ;
2013-05-02 07:55:47 -07:00
}
2013-01-24 04:55:32 -08:00
self . spinner . show ( ) ;
self . evalStats . empty ( ) ;
var startTime = new Date ( ) . getTime ( ) ;
2013-04-17 07:49:21 -07:00
var rangeSeconds = self . parseDuration ( self . rangeInput . val ( ) ) ;
2017-06-19 09:22:59 -07:00
var resolution = parseInt ( self . queryForm . find ( "input[name=step_input]" ) . val ( ) ) || Math . max ( Math . floor ( rangeSeconds / 250 ) , 1 ) ;
2013-03-21 09:36:04 -07:00
var endDate = self . getEndDate ( ) / 1000 ;
2018-10-26 05:16:40 -07:00
var moment = self . getMoment ( ) / 1000 ;
2013-01-24 04:55:32 -08:00
2013-04-17 07:49:21 -07:00
if ( self . queryXhr ) {
2013-07-23 17:18:49 -07:00
self . queryXhr . abort ( ) ;
2013-04-17 07:49:21 -07:00
}
2013-07-23 17:18:49 -07:00
var url ;
var success ;
2015-06-25 03:42:18 -07:00
var params = {
"query" : self . expr . val ( )
} ;
2015-11-11 11:04:37 -08:00
if ( self . options . tab === 0 ) {
params . start = endDate - rangeSeconds ;
params . end = endDate ;
params . step = resolution ;
2015-06-25 03:42:18 -07:00
url = PATH _PREFIX + "/api/v1/query_range" ;
2013-07-23 17:18:49 -07:00
success = function ( json , textStatus ) { self . handleGraphResponse ( json , textStatus ) ; } ;
2013-04-26 07:50:59 -07:00
} else {
2018-10-26 05:16:40 -07:00
params . time = moment ;
2015-06-25 03:42:18 -07:00
url = PATH _PREFIX + "/api/v1/query" ;
success = function ( json , textStatus ) { self . handleConsoleResponse ( json , textStatus ) ; } ;
2013-04-26 07:50:59 -07:00
}
2017-05-26 08:17:48 -07:00
self . params = params ;
2013-04-26 07:50:59 -07:00
2013-04-17 07:49:21 -07:00
self . queryXhr = $ . ajax ( {
2013-01-24 04:55:32 -08:00
method : self . queryForm . attr ( "method" ) ,
2013-04-26 07:50:59 -07:00
url : url ,
2013-07-23 17:18:49 -07:00
dataType : "json" ,
2015-06-25 03:42:18 -07:00
data : params ,
success : function ( json , textStatus ) {
if ( json . status !== "success" ) {
self . showError ( json . error ) ;
return ;
}
2018-02-14 22:23:12 -08:00
2019-08-28 06:25:28 -07:00
if ( "warnings" in json && json . warnings . length > 0 ) {
2019-11-05 23:55:28 -08:00
self . showWarning ( json . warnings . map ( escapeHTML ) . join ( '<br>' ) ) ;
2019-08-28 06:25:28 -07:00
}
2017-12-21 09:58:05 -08:00
queryHistory . handleHistory ( self ) ;
2015-06-25 03:42:18 -07:00
success ( json . data , textStatus ) ;
} ,
2013-04-26 07:50:59 -07:00
error : function ( xhr , resp ) {
2014-10-15 07:38:09 -07:00
if ( resp != "abort" ) {
2015-06-25 03:42:18 -07:00
var err ;
if ( xhr . responseJSON !== undefined ) {
err = xhr . responseJSON . error ;
} else {
err = xhr . statusText ;
}
self . showError ( "Error executing query: " + err ) ;
2014-10-15 07:38:09 -07:00
}
2013-01-24 04:55:32 -08:00
} ,
2016-10-12 17:05:50 -07:00
complete : function ( xhr , resp ) {
if ( resp == "abort" ) {
return ;
}
2013-01-24 04:55:32 -08:00
var duration = new Date ( ) . getTime ( ) - startTime ;
2017-05-19 06:21:55 -07:00
var totalTimeSeries = 0 ;
if ( xhr . responseJSON . data !== undefined ) {
if ( xhr . responseJSON . data . resultType === "scalar" ) {
totalTimeSeries = 1 ;
2017-11-30 02:04:02 -08:00
} else if ( xhr . responseJSON . data . result !== null ) {
2017-05-19 06:21:55 -07:00
totalTimeSeries = xhr . responseJSON . data . result . length ;
}
}
2017-04-03 03:52:25 -07:00
self . evalStats . html ( "Load time: " + duration + "ms <br /> Resolution: " + resolution + "s <br />" + "Total time series: " + totalTimeSeries ) ;
2013-01-24 04:55:32 -08:00
self . spinner . hide ( ) ;
}
} ) ;
} ;
2014-10-30 08:18:05 -07:00
Prometheus . Graph . prototype . showError = function ( msg ) {
var self = this ;
self . error . text ( msg ) ;
self . error . show ( ) ;
2015-11-11 11:04:37 -08:00
} ;
2014-10-30 08:18:05 -07:00
2019-01-31 09:03:50 -08:00
Prometheus . Graph . prototype . clearError = function ( ) {
2014-10-30 08:18:05 -07:00
var self = this ;
self . error . text ( '' ) ;
self . error . hide ( ) ;
2015-11-11 11:04:37 -08:00
} ;
2014-10-30 08:18:05 -07:00
2019-01-31 09:03:50 -08:00
Prometheus . Graph . prototype . showWarning = function ( msg ) {
var self = this ;
self . warning . html ( msg ) ;
self . warning . show ( ) ;
} ;
Prometheus . Graph . prototype . clearWarning = function ( ) {
var self = this ;
self . warning . html ( '' ) ;
self . warning . hide ( ) ;
} ;
2013-04-17 07:49:21 -07:00
Prometheus . Graph . prototype . updateRefresh = function ( ) {
var self = this ;
if ( self . timeoutID ) {
2013-07-23 17:21:31 -07:00
window . clearTimeout ( self . timeoutID ) ;
2013-04-17 07:49:21 -07:00
}
interval = self . parseDuration ( self . refreshInterval . val ( ) ) ;
2015-11-11 11:04:37 -08:00
if ( ! interval ) { return ; }
2013-04-17 07:49:21 -07:00
self . timeoutID = window . setTimeout ( function ( ) {
2013-07-23 17:21:31 -07:00
self . submitQuery ( ) ;
self . updateRefresh ( ) ;
} , interval * SECOND ) ;
2015-11-11 11:04:37 -08:00
} ;
2013-04-17 07:49:21 -07:00
2013-04-12 01:39:15 -07:00
Prometheus . Graph . prototype . renderLabels = function ( labels ) {
2013-01-24 04:55:32 -08:00
var labelStrings = [ ] ;
2015-11-11 11:04:37 -08:00
for ( var label in labels ) {
2014-03-14 04:51:33 -07:00
if ( label != "__name__" ) {
2015-03-22 13:59:14 -07:00
labelStrings . push ( "<strong>" + label + "</strong>: " + escapeHTML ( labels [ label ] ) ) ;
2013-01-24 04:55:32 -08:00
}
}
2013-07-23 17:21:31 -07:00
return labels = "<div class=\"labels\">" + labelStrings . join ( "<br>" ) + "</div>" ;
2015-11-11 11:04:37 -08:00
} ;
2013-04-12 01:39:15 -07:00
Prometheus . Graph . prototype . metricToTsName = function ( labels ) {
2015-11-11 11:04:37 -08:00
var tsName = ( labels . _ _name _ _ || '' ) + "{" ;
2013-04-15 01:04:09 -07:00
var labelStrings = [ ] ;
2015-11-11 11:04:37 -08:00
for ( var label in labels ) {
2014-03-14 04:51:33 -07:00
if ( label != "__name__" ) {
2015-11-11 11:04:37 -08:00
labelStrings . push ( label + "=\"" + labels [ label ] + "\"" ) ;
2013-04-15 01:04:09 -07:00
}
}
tsName += labelStrings . join ( "," ) + "}" ;
return tsName ;
2013-01-24 04:55:32 -08:00
} ;
Prometheus . Graph . prototype . parseValue = function ( value ) {
2015-03-16 14:50:35 -07:00
var val = parseFloat ( value ) ;
if ( isNaN ( val ) ) {
// "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). The
// can't be graphed, so show them as gaps (null).
2015-11-11 11:04:37 -08:00
return null ;
2013-01-24 04:55:32 -08:00
}
2015-03-16 14:50:35 -07:00
return val ;
2013-01-24 04:55:32 -08:00
} ;
Prometheus . Graph . prototype . transformData = function ( json ) {
2015-03-16 14:50:35 -07:00
var self = this ;
2013-01-24 04:55:32 -08:00
var palette = new Rickshaw . Color . Palette ( ) ;
2015-06-25 03:42:18 -07:00
if ( json . resultType != "matrix" ) {
2014-10-30 08:18:05 -07:00
self . showError ( "Result is not of matrix type! Please enter a correct expression." ) ;
2013-01-24 04:55:32 -08:00
return [ ] ;
}
2015-06-25 03:42:18 -07:00
var data = json . result . map ( function ( ts ) {
var name ;
var labels ;
if ( ts . metric === null ) {
name = "scalar" ;
labels = { } ;
} else {
name = escapeHTML ( self . metricToTsName ( ts . metric ) ) ;
labels = ts . metric ;
}
2013-01-24 04:55:32 -08:00
return {
2015-06-25 03:42:18 -07:00
name : name ,
labels : labels ,
More efficient JSON query result format.
This depends on https://github.com/prometheus/client_golang/pull/51.
For vectors, the result format looks like this:
```json
{
"version": 1,
"type" : "vector",
"value" : [
{
"timestamp" : 1421765411.045,
"value" : "65.475000",
"metric" : {
"quantile" : "0.5",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "304"
}
},
{
"timestamp" : 1421765411.045,
"value" : "5826.339000",
"metric" : {
"quantile" : "0.9",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "prometheus",
"method" : "get",
"code" : "200"
}
},
/* ... */
]
}
```
For matrices, it looks like this:
```json
{
"version": 1,
"type" : "matrix",
"value" : [
{
"metric" : {
"quantile" : "0.99",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "200"
},
"values" : [
[
1421765547.659,
"29162.953000"
],
[
1421765548.659,
"29162.953000"
],
[
1421765549.659,
"29162.953000"
],
/* ... */
]
}
]
}
```
2015-01-20 06:13:49 -08:00
data : ts . values . map ( function ( value ) {
2013-01-24 04:55:32 -08:00
return {
More efficient JSON query result format.
This depends on https://github.com/prometheus/client_golang/pull/51.
For vectors, the result format looks like this:
```json
{
"version": 1,
"type" : "vector",
"value" : [
{
"timestamp" : 1421765411.045,
"value" : "65.475000",
"metric" : {
"quantile" : "0.5",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "304"
}
},
{
"timestamp" : 1421765411.045,
"value" : "5826.339000",
"metric" : {
"quantile" : "0.9",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "prometheus",
"method" : "get",
"code" : "200"
}
},
/* ... */
]
}
```
For matrices, it looks like this:
```json
{
"version": 1,
"type" : "matrix",
"value" : [
{
"metric" : {
"quantile" : "0.99",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "200"
},
"values" : [
[
1421765547.659,
"29162.953000"
],
[
1421765548.659,
"29162.953000"
],
[
1421765549.659,
"29162.953000"
],
/* ... */
]
}
]
}
```
2015-01-20 06:13:49 -08:00
x : value [ 0 ] ,
y : self . parseValue ( value [ 1 ] )
2015-11-11 11:04:37 -08:00
} ;
2013-01-24 04:55:32 -08:00
} ) ,
color : palette . color ( )
} ;
} ) ;
2017-05-26 08:17:48 -07:00
data . forEach ( function ( s ) {
// Insert nulls for all missing steps.
var newSeries = [ ] ;
var pos = 0 ;
for ( var t = self . params . start ; t <= self . params . end ; t += self . params . step ) {
// Allow for floating point inaccuracy.
if ( s . data . length > pos && s . data [ pos ] . x < t + self . params . step / 100 ) {
newSeries . push ( s . data [ pos ] ) ;
pos ++ ;
} else {
newSeries . push ( { x : t , y : null } ) ;
}
}
s . data = newSeries ;
} ) ;
2013-01-24 04:55:32 -08:00
return data ;
} ;
2015-02-06 07:20:46 -08:00
Prometheus . Graph . prototype . updateGraph = function ( ) {
2013-01-24 04:55:32 -08:00
var self = this ;
2015-11-11 11:04:37 -08:00
if ( self . data . length === 0 ) { return ; }
2015-02-06 07:20:46 -08:00
// Remove any traces of an existing graph.
self . legend . empty ( ) ;
if ( self . graphArea . children ( ) . length > 0 ) {
self . graph . remove ( ) ;
self . yAxis . remove ( ) ;
}
self . graph = $ ( '<div class="graph"></div>' ) ;
self . yAxis = $ ( '<div class="y_axis"></div>' ) ;
self . graphArea . append ( self . graph ) ;
self . graphArea . append ( self . yAxis ) ;
2015-06-25 03:42:18 -07:00
var endTime = self . getEndDate ( ) / 1000 ; // Convert to UNIX timestamp.
2015-06-12 08:20:02 -07:00
var duration = self . parseDuration ( self . rangeInput . val ( ) ) || 3600 ; // 1h default.
var startTime = endTime - duration ;
self . data . forEach ( function ( s ) {
/ / P a d d i n g s e r i e s w i t h i n v i s i b l e " n u l l " v a l u e s a t t h e c o n f i g u r e d x - a x i s b o u n d a r i e s e n s u r e s
// that graphs are displayed with a fixed x-axis range instead of snapping to the available
// time range in the data.
if ( s . data [ 0 ] . x > startTime ) {
s . data . unshift ( { x : startTime , y : null } ) ;
}
if ( s . data [ s . data . length - 1 ] . x < endTime ) {
s . data . push ( { x : endTime , y : null } ) ;
}
} ) ;
2015-02-06 07:20:46 -08:00
// Now create the new graph.
2013-01-24 04:55:32 -08:00
self . rickshawGraph = new Rickshaw . Graph ( {
element : self . graph [ 0 ] ,
2013-03-21 11:12:04 -07:00
height : Math . max ( self . graph . innerHeight ( ) , 100 ) ,
2013-07-01 05:47:43 -07:00
width : Math . max ( self . graph . innerWidth ( ) - 80 , 200 ) ,
2015-01-14 08:56:03 -08:00
renderer : ( self . isStacked ( ) ? "stack" : "line" ) ,
2013-01-24 04:55:32 -08:00
interpolation : "linear" ,
2013-05-21 07:54:33 -07:00
series : self . data ,
min : "auto" ,
2013-01-24 04:55:32 -08:00
} ) ;
2017-04-12 06:25:25 -07:00
// Find and set graph's max/min
2017-05-18 07:03:58 -07:00
if ( self . isStacked ( ) === true ) {
// When stacked is toggled
var max = 0 ;
self . data . forEach ( function ( timeSeries ) {
var currSeriesMax = 0 ;
timeSeries . data . forEach ( function ( dataPoint ) {
if ( dataPoint . y > currSeriesMax && dataPoint . y != null ) {
currSeriesMax = dataPoint . y ;
}
} ) ;
max += currSeriesMax ;
} ) ;
self . rickshawGraph . max = max * 1.05 ;
self . rickshawGraph . min = 0 ;
} else {
var min = Infinity ;
var max = - Infinity ;
self . data . forEach ( function ( timeSeries ) {
timeSeries . data . forEach ( function ( dataPoint ) {
2017-04-12 06:25:25 -07:00
if ( dataPoint . y < min && dataPoint . y != null ) {
min = dataPoint . y ;
}
if ( dataPoint . y > max && dataPoint . y != null ) {
max = dataPoint . y ;
}
2017-05-18 07:03:58 -07:00
} ) ;
2017-04-12 06:25:25 -07:00
} ) ;
2019-09-12 00:42:19 -07:00
var offset = 0.1 * ( min === max ? max : Math . abs ( max - min ) ) ;
self . rickshawGraph . max = max + offset ;
self . rickshawGraph . min = min - offset ;
2017-04-12 06:25:25 -07:00
}
2013-01-24 04:55:32 -08:00
var xAxis = new Rickshaw . Graph . Axis . Time ( { graph : self . rickshawGraph } ) ;
var yAxis = new Rickshaw . Graph . Axis . Y ( {
graph : self . rickshawGraph ,
2013-07-01 05:47:43 -07:00
orientation : "left" ,
2017-09-16 08:16:40 -07:00
tickFormat : this . formatKMBT ,
2013-07-01 05:47:43 -07:00
element : self . yAxis [ 0 ] ,
2013-01-24 04:55:32 -08:00
} ) ;
self . rickshawGraph . render ( ) ;
var hoverDetail = new Rickshaw . Graph . HoverDetail ( {
2013-04-11 09:17:33 -07:00
graph : self . rickshawGraph ,
formatter : function ( series , x , y ) {
2016-04-22 14:00:18 -07:00
var date = '<span class="date">' + new Date ( x * 1000 ) . toUTCString ( ) + '</span>' ;
2013-04-11 09:17:33 -07:00
var swatch = '<span class="detail_swatch" style="background-color: ' + series . color + '"></span>' ;
2016-04-22 14:00:18 -07:00
var content = swatch + ( series . labels . _ _name _ _ || 'value' ) + ": <strong>" + y + '</strong>' ;
return date + '<br>' + content + '<br>' + self . renderLabels ( series . labels ) ;
2015-02-06 06:02:13 -08:00
}
2013-01-24 04:55:32 -08:00
} ) ;
var legend = new Rickshaw . Graph . Legend ( {
element : self . legend [ 0 ] ,
2013-04-12 01:39:15 -07:00
graph : self . rickshawGraph ,
2013-01-24 04:55:32 -08:00
} ) ;
2013-04-11 09:21:00 -07:00
var highlighter = new Rickshaw . Graph . Behavior . Series . Highlight ( {
graph : self . rickshawGraph ,
legend : legend
} ) ;
2013-01-24 04:55:32 -08:00
var shelving = new Rickshaw . Graph . Behavior . Series . Toggle ( {
graph : self . rickshawGraph ,
legend : legend
} ) ;
2016-09-03 12:05:23 -07:00
self . handleChange ( ) ;
2013-01-24 04:55:32 -08:00
} ;
2013-03-26 06:07:56 -07:00
2013-03-21 11:12:04 -07:00
Prometheus . Graph . prototype . resizeGraph = function ( ) {
var self = this ;
2015-11-11 11:04:37 -08:00
if ( self . rickshawGraph !== null ) {
2013-07-23 17:20:42 -07:00
self . rickshawGraph . configure ( {
width : Math . max ( self . graph . innerWidth ( ) - 80 , 200 ) ,
} ) ;
self . rickshawGraph . render ( ) ;
}
2015-11-11 11:04:37 -08:00
} ;
2013-04-26 07:50:59 -07:00
Prometheus . Graph . prototype . handleGraphResponse = function ( json , textStatus ) {
2015-11-11 11:04:37 -08:00
var self = this ;
2015-02-06 07:20:46 -08:00
// Rickshaw mutates passed series data for stacked graphs, so we need to save
// the original AJAX response in order to re-transform it into series data
// when the user disables the stacking.
self . graphJSON = json ;
2013-04-26 07:50:59 -07:00
self . data = self . transformData ( json ) ;
2015-11-11 11:04:37 -08:00
if ( self . data . length === 0 ) {
2014-10-30 08:18:05 -07:00
self . showError ( "No datapoints found." ) ;
2013-04-26 07:50:59 -07:00
return ;
}
2013-07-23 17:21:31 -07:00
self . graphTab . removeClass ( "reload" ) ;
2015-02-06 07:20:46 -08:00
self . updateGraph ( ) ;
2015-11-11 11:04:37 -08:00
} ;
2013-04-26 07:50:59 -07:00
2013-07-23 17:18:49 -07:00
Prometheus . Graph . prototype . handleConsoleResponse = function ( data , textStatus ) {
2013-04-26 07:50:59 -07:00
var self = this ;
2013-07-23 17:18:49 -07:00
self . consoleTab . removeClass ( "reload" ) ;
2015-02-06 07:20:46 -08:00
self . graphJSON = null ;
2013-07-23 17:18:49 -07:00
var tBody = self . consoleTab . find ( ".console_table tbody" ) ;
tBody . empty ( ) ;
2015-06-25 03:42:18 -07:00
switch ( data . resultType ) {
2013-07-23 17:18:49 -07:00
case "vector" :
2017-11-30 02:04:02 -08:00
if ( data . result === null || data . result . length === 0 ) {
2015-01-19 05:27:07 -08:00
tBody . append ( "<tr><td colspan='2'><i>no data</i></td></tr>" ) ;
2015-01-14 08:56:03 -08:00
return ;
}
2019-01-31 09:03:50 -08:00
data . result = self . limitSeries ( data . result ) ;
2015-06-25 03:42:18 -07:00
for ( var i = 0 ; i < data . result . length ; i ++ ) {
var s = data . result [ i ] ;
var tsName = self . metricToTsName ( s . metric ) ;
2015-07-09 15:43:43 -07:00
tBody . append ( "<tr><td>" + escapeHTML ( tsName ) + "</td><td>" + s . value [ 1 ] + "</td></tr>" ) ;
2013-07-23 17:18:49 -07:00
}
break ;
case "matrix" :
2015-06-25 03:42:18 -07:00
if ( data . result . length === 0 ) {
2015-01-19 05:27:07 -08:00
tBody . append ( "<tr><td colspan='2'><i>no data</i></td></tr>" ) ;
2015-01-14 08:56:03 -08:00
return ;
}
2019-01-31 09:03:50 -08:00
data . result = self . limitSeries ( data . result ) ;
2015-06-25 03:42:18 -07:00
for ( var i = 0 ; i < data . result . length ; i ++ ) {
var v = data . result [ i ] ;
More efficient JSON query result format.
This depends on https://github.com/prometheus/client_golang/pull/51.
For vectors, the result format looks like this:
```json
{
"version": 1,
"type" : "vector",
"value" : [
{
"timestamp" : 1421765411.045,
"value" : "65.475000",
"metric" : {
"quantile" : "0.5",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "304"
}
},
{
"timestamp" : 1421765411.045,
"value" : "5826.339000",
"metric" : {
"quantile" : "0.9",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "prometheus",
"method" : "get",
"code" : "200"
}
},
/* ... */
]
}
```
For matrices, it looks like this:
```json
{
"version": 1,
"type" : "matrix",
"value" : [
{
"metric" : {
"quantile" : "0.99",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "200"
},
"values" : [
[
1421765547.659,
"29162.953000"
],
[
1421765548.659,
"29162.953000"
],
[
1421765549.659,
"29162.953000"
],
/* ... */
]
}
]
}
```
2015-01-20 06:13:49 -08:00
var tsName = self . metricToTsName ( v . metric ) ;
2013-07-23 17:18:49 -07:00
var valueText = "" ;
More efficient JSON query result format.
This depends on https://github.com/prometheus/client_golang/pull/51.
For vectors, the result format looks like this:
```json
{
"version": 1,
"type" : "vector",
"value" : [
{
"timestamp" : 1421765411.045,
"value" : "65.475000",
"metric" : {
"quantile" : "0.5",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "304"
}
},
{
"timestamp" : 1421765411.045,
"value" : "5826.339000",
"metric" : {
"quantile" : "0.9",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "prometheus",
"method" : "get",
"code" : "200"
}
},
/* ... */
]
}
```
For matrices, it looks like this:
```json
{
"version": 1,
"type" : "matrix",
"value" : [
{
"metric" : {
"quantile" : "0.99",
"instance" : "http://localhost:9090/metrics",
"job" : "prometheus",
"__name__" : "http_request_duration_microseconds",
"handler" : "/static/",
"method" : "get",
"code" : "200"
},
"values" : [
[
1421765547.659,
"29162.953000"
],
[
1421765548.659,
"29162.953000"
],
[
1421765549.659,
"29162.953000"
],
/* ... */
]
}
]
}
```
2015-01-20 06:13:49 -08:00
for ( var j = 0 ; j < v . values . length ; j ++ ) {
valueText += v . values [ j ] [ 1 ] + " @" + v . values [ j ] [ 0 ] + "<br/>" ;
2013-07-23 17:18:49 -07:00
}
2015-11-11 11:04:37 -08:00
tBody . append ( "<tr><td>" + escapeHTML ( tsName ) + "</td><td>" + valueText + "</td></tr>" ) ;
2013-07-23 17:18:49 -07:00
}
break ;
case "scalar" :
2015-07-06 01:24:50 -07:00
tBody . append ( "<tr><td>scalar</td><td>" + data . result [ 1 ] + "</td></tr>" ) ;
2013-07-23 17:18:49 -07:00
break ;
2015-06-25 03:42:18 -07:00
case "string" :
2015-10-01 04:16:49 -07:00
tBody . append ( "<tr><td>string</td><td>" + escapeHTML ( data . result [ 1 ] ) + "</td></tr>" ) ;
2013-08-02 00:04:13 -07:00
break ;
2013-07-23 17:18:49 -07:00
default :
2014-10-30 08:18:05 -07:00
self . showError ( "Unsupported value type!" ) ;
2013-07-23 17:18:49 -07:00
break ;
2013-07-13 10:30:51 -07:00
}
2018-10-26 05:16:40 -07:00
self . handleChange ( ) ;
2015-11-11 11:04:37 -08:00
} ;
2013-01-24 04:55:32 -08:00
2016-07-31 07:30:23 -07:00
Prometheus . Graph . prototype . remove = function ( ) {
var self = this ;
2016-08-05 14:35:11 -07:00
$ ( self . graphHTML ) . remove ( ) ;
2016-09-03 12:05:23 -07:00
self . handleRemove ( ) ;
self . handleChange ( ) ;
2016-08-05 07:35:38 -07:00
} ;
2017-09-16 08:16:40 -07:00
Prometheus . Graph . prototype . formatKMBT = function ( y ) {
var abs _y = Math . abs ( y ) ;
2017-09-18 06:55:22 -07:00
if ( abs _y >= 1e24 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e24 ) . toString ( ) + "Y" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y >= 1e21 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e21 ) . toString ( ) + "Z" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y >= 1e18 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e18 ) . toString ( ) + "E" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y >= 1e15 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e15 ) . toString ( ) + "P" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y >= 1e12 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e12 ) . toString ( ) + "T" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y >= 1e9 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e9 ) . toString ( ) + "G" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y >= 1e6 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e6 ) . toString ( ) + "M" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y >= 1e3 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e3 ) . toString ( ) + "k" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y >= 1 ) {
return y
} else if ( abs _y === 0 ) {
return y
2017-09-18 06:55:22 -07:00
} else if ( abs _y <= 1e-24 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-24 ) . toString ( ) + "y" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y <= 1e-21 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-21 ) . toString ( ) + "z" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y <= 1e-18 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-18 ) . toString ( ) + "a" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y <= 1e-15 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-15 ) . toString ( ) + "f" ;
2017-09-18 06:55:22 -07:00
} else if ( abs _y <= 1e-12 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-12 ) . toString ( ) + "p" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y <= 1e-9 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-9 ) . toString ( ) + "n" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y <= 1e-6 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-6 ) . toString ( ) + "µ" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y <= 1e-3 ) {
2017-09-19 09:53:00 -07:00
return ( y / 1e-3 ) . toString ( ) + "m" ;
2017-09-16 08:16:40 -07:00
} else if ( abs _y <= 1 ) {
2017-09-19 09:53:00 -07:00
return y
2017-09-16 08:16:40 -07:00
}
}
2019-01-31 09:03:50 -08:00
Prometheus . Graph . prototype . limitSeries = function ( result ) {
var self = this ;
var MAX _SERIES _NUM = 10000 ;
var message = "<strong>Warning!</strong> Fetched " +
result . length + " series, but displaying only first " +
MAX _SERIES _NUM + "." ;
if ( result . length > MAX _SERIES _NUM ) {
self . showWarning ( message ) ;
return result . splice ( 0 , MAX _SERIES _NUM ) ;
}
return result ;
}
2017-12-21 09:58:05 -08:00
/ * *
* Page
* /
const pageConfig = {
2018-02-14 22:23:12 -08:00
allMetrics : [ ] ,
2018-02-02 10:16:29 -08:00
graphs : [ ] ,
queryHistMetrics : JSON . parse ( localStorage . getItem ( 'history' ) ) || [ ] ,
2016-09-03 12:05:23 -07:00
} ;
2017-12-21 09:58:05 -08:00
Prometheus . Page = function ( ) { } ;
2016-09-03 12:05:23 -07:00
Prometheus . Page . prototype . init = function ( ) {
var graphOptions = this . parseURL ( ) ;
if ( graphOptions . length === 0 ) {
graphOptions . push ( { } ) ;
}
2020-09-30 02:06:42 -07:00
var pageInstance = this ;
2016-09-03 12:05:23 -07:00
graphOptions . forEach ( this . addGraph , this ) ;
2020-09-30 02:06:42 -07:00
$ ( "#add_graph" ) . click ( function ( ) {
pageInstance . addGraph ( { } ) ;
} ) ;
2016-09-03 12:05:23 -07:00
} ;
Prometheus . Page . prototype . parseURL = function ( ) {
if ( window . location . search == "" ) {
return [ ] ;
}
var queryParams = window . location . search . substring ( 1 ) . split ( '&' ) ;
var queryParamHelper = new Prometheus . Page . QueryParamHelper ( ) ;
return queryParamHelper . parseQueryParams ( queryParams ) ;
} ;
Prometheus . Page . prototype . addGraph = function ( options ) {
var graph = new Prometheus . Graph (
$ ( "#graph_container" ) ,
options ,
this . updateURL . bind ( this ) ,
this . removeGraph . bind ( this )
) ;
2017-12-21 09:58:05 -08:00
pageConfig . graphs . push ( graph ) ;
2016-09-03 12:05:23 -07:00
$ ( window ) . resize ( function ( ) {
graph . resizeGraph ( ) ;
} ) ;
} ;
// NOTE: This needs to be kept in sync with /util/strutil/strconv.go:GraphLinkForExpression
Prometheus . Page . prototype . updateURL = function ( ) {
2017-12-21 09:58:05 -08:00
var queryString = pageConfig . graphs . map ( function ( graph , index ) {
2016-09-03 12:05:23 -07:00
var graphOptions = graph . getOptions ( ) ;
var queryParamHelper = new Prometheus . Page . QueryParamHelper ( ) ;
var queryObject = queryParamHelper . generateQueryObject ( graphOptions , index ) ;
return $ . param ( queryObject ) ;
} , this ) . join ( "&" ) ;
history . pushState ( { } , "" , "graph?" + queryString ) ;
} ;
Prometheus . Page . prototype . removeGraph = function ( graph ) {
2017-12-21 09:58:05 -08:00
pageConfig . graphs = pageConfig . graphs . filter ( function ( g ) { return g !== graph } ) ;
2016-09-03 12:05:23 -07:00
} ;
Prometheus . Page . QueryParamHelper = function ( ) { } ;
Prometheus . Page . QueryParamHelper . prototype . parseQueryParams = function ( queryParams ) {
var orderedQueryParams = this . filterInvalidParams ( queryParams ) . sort ( ) ;
return this . fetchOptionsFromOrderedParams ( orderedQueryParams , 0 ) ;
} ;
Prometheus . Page . QueryParamHelper . queryParamFormat = /^g\d+\..+=.+$/ ;
Prometheus . Page . QueryParamHelper . prototype . filterInvalidParams = function ( paramTuples ) {
return paramTuples . filter ( function ( paramTuple ) {
return Prometheus . Page . QueryParamHelper . queryParamFormat . test ( paramTuple ) ;
} ) ;
} ;
Prometheus . Page . QueryParamHelper . prototype . fetchOptionsFromOrderedParams = function ( queryParams , graphIndex ) {
if ( queryParams . length == 0 ) {
return [ ] ;
}
var prefixOfThisIndex = this . queryParamPrefix ( graphIndex ) ;
var numberOfParamsForThisGraph = queryParams . filter ( function ( paramTuple ) {
return paramTuple . startsWith ( prefixOfThisIndex ) ;
} ) . length ;
if ( numberOfParamsForThisGraph == 0 ) {
return [ ] ;
}
var paramsForThisGraph = queryParams . splice ( 0 , numberOfParamsForThisGraph ) ;
paramsForThisGraph = paramsForThisGraph . map ( function ( paramTuple ) {
return paramTuple . substring ( prefixOfThisIndex . length ) ;
} ) ;
var options = this . parseQueryParamsOfOneGraph ( paramsForThisGraph ) ;
var optionAccumulator = this . fetchOptionsFromOrderedParams ( queryParams , graphIndex + 1 ) ;
optionAccumulator . unshift ( options ) ;
return optionAccumulator ;
} ;
Prometheus . Page . QueryParamHelper . prototype . parseQueryParamsOfOneGraph = function ( queryParams ) {
var options = { } ;
queryParams . forEach ( function ( tuple ) {
var optionNameAndValue = tuple . split ( '=' ) ;
var optionName = optionNameAndValue [ 0 ] ;
2016-10-21 08:49:06 -07:00
var optionValue = decodeURIComponent ( optionNameAndValue [ 1 ] . replace ( /\+/g , " " ) ) ;
2016-09-03 12:05:23 -07:00
if ( optionName == "tab" ) {
optionValue = parseInt ( optionValue ) ; // tab is integer
}
options [ optionName ] = optionValue ;
} ) ;
return options ;
} ;
Prometheus . Page . QueryParamHelper . prototype . queryParamPrefix = function ( index ) {
return "g" + index + "." ;
} ;
Prometheus . Page . QueryParamHelper . prototype . generateQueryObject = function ( graphOptions , index ) {
var prefix = this . queryParamPrefix ( index ) ;
var queryObject = { } ;
Object . keys ( graphOptions ) . forEach ( function ( key ) {
queryObject [ prefix + key ] = graphOptions [ key ] ;
} ) ;
return queryObject ;
} ;
2016-09-16 14:01:21 -07:00
// These two methods (isDeprecatedGraphURL and redirectToMigratedURL)
// are added only for backward compatibility to old query format.
function isDeprecatedGraphURL ( ) {
if ( window . location . hash . length == 0 ) {
return false ;
}
var decodedFragment = decodeURIComponent ( window . location . hash ) ;
try {
JSON . parse ( decodedFragment . substr ( 1 ) ) ; // drop the hash #
} catch ( e ) {
return false ;
}
return true ;
}
function redirectToMigratedURL ( ) {
var decodedFragment = decodeURIComponent ( window . location . hash ) ;
var graphOptions = JSON . parse ( decodedFragment . substr ( 1 ) ) ; // drop the hash #
var queryObject = { } ;
graphOptions . map ( function ( options , index ) {
var prefix = "g" + index + "." ;
Object . keys ( options ) . forEach ( function ( key ) {
queryObject [ prefix + key ] = options [ key ] ;
} ) ;
} ) ;
var query = $ . param ( queryObject ) ;
2017-07-11 10:36:17 -07:00
window . location = PATH _PREFIX + "/graph?" + query ;
2016-09-16 14:01:21 -07:00
}
2017-12-21 09:58:05 -08:00
/ * *
* Query History helper functions
* * * /
const queryHistory = {
2018-02-02 10:16:29 -08:00
isEnabled : function ( ) {
return JSON . parse ( localStorage . getItem ( 'enable-query-history' ) )
} ,
2017-12-21 09:58:05 -08:00
bindHistoryEvents : function ( graph ) {
const targetEl = $ ( 'div.query-history' ) ;
const icon = $ ( targetEl ) . children ( 'i' ) ;
targetEl . off ( 'click' ) ;
2018-02-02 10:16:29 -08:00
if ( queryHistory . isEnabled ( ) ) {
2017-12-21 09:58:05 -08:00
this . toggleOn ( targetEl ) ;
}
targetEl . on ( 'click' , function ( ) {
if ( icon . hasClass ( 'glyphicon-unchecked' ) ) {
queryHistory . toggleOn ( targetEl ) ;
} else if ( icon . hasClass ( 'glyphicon-check' ) ) {
queryHistory . toggleOff ( targetEl ) ;
}
} ) ;
} ,
handleHistory : function ( graph ) {
const query = graph . expr . val ( ) ;
const isSimpleMetric = pageConfig . allMetrics . indexOf ( query ) !== - 1 ;
if ( isSimpleMetric ) {
return ;
}
let parsedQueryHistory = JSON . parse ( localStorage . getItem ( 'history' ) ) ;
const hasStoredQuery = parsedQueryHistory . indexOf ( query ) !== - 1 ;
if ( hasStoredQuery ) {
parsedQueryHistory . splice ( parsedQueryHistory . indexOf ( query ) , 1 ) ;
}
parsedQueryHistory . push ( query ) ;
const queryCount = parsedQueryHistory . length ;
parsedQueryHistory = parsedQueryHistory . slice ( queryCount - 50 , queryCount ) ;
localStorage . setItem ( 'history' , JSON . stringify ( parsedQueryHistory ) ) ;
2018-02-02 10:16:29 -08:00
pageConfig . queryHistMetrics = parsedQueryHistory ;
if ( queryHistory . isEnabled ( ) ) {
this . updateTypeaheadMetricSet ( pageConfig . queryHistMetrics . concat ( pageConfig . allMetrics ) ) ;
}
2017-12-21 09:58:05 -08:00
} ,
toggleOn : function ( targetEl ) {
2018-02-02 10:16:29 -08:00
this . updateTypeaheadMetricSet ( pageConfig . queryHistMetrics . concat ( pageConfig . allMetrics ) ) ;
2017-12-21 09:58:05 -08:00
$ ( targetEl ) . children ( 'i' ) . removeClass ( 'glyphicon-unchecked' ) . addClass ( 'glyphicon-check' ) ;
targetEl . addClass ( 'is-checked' ) ;
localStorage . setItem ( 'enable-query-history' , true ) ;
} ,
toggleOff : function ( targetEl ) {
2018-02-02 10:16:29 -08:00
this . updateTypeaheadMetricSet ( pageConfig . allMetrics ) ;
2017-12-21 09:58:05 -08:00
$ ( targetEl ) . children ( 'i' ) . removeClass ( 'glyphicon-check' ) . addClass ( 'glyphicon-unchecked' ) ;
targetEl . removeClass ( 'is-checked' ) ;
localStorage . setItem ( 'enable-query-history' , false ) ;
} ,
updateTypeaheadMetricSet : function ( metricSet ) {
pageConfig . graphs . forEach ( function ( graph ) {
2018-02-14 22:23:12 -08:00
if ( graph . expr . data ( 'typeahead' ) ) {
graph . expr . data ( 'typeahead' ) . source = metricSet ;
}
2017-12-21 09:58:05 -08:00
} ) ;
}
} ;
2019-03-06 07:16:55 -08:00
// Defers invocation of fn for waitPeriod if returned function is called repeatedly
function debounce ( fn , waitPeriod ) {
var timeout ;
return function ( ) {
clearTimeout ( timeout ) ;
var args = arguments ;
var scope = this ;
function invokeFn ( ) {
return fn . apply ( scope , args ) ;
}
timeout = setTimeout ( invokeFn , waitPeriod ) ;
} ;
}
2017-12-21 09:58:05 -08:00
function escapeHTML ( string ) {
var entityMap = {
"&" : "&" ,
"<" : "<" ,
">" : ">" ,
'"' : '"' ,
"'" : ''' ,
"/" : '/'
} ;
return String ( string ) . replace ( /[&<>"'\/]/g , function ( s ) {
return entityMap [ s ] ;
} ) ;
}
function init ( ) {
$ . ajaxSetup ( {
cache : false
} ) ;
$ . ajax ( {
2020-11-03 05:51:48 -08:00
url : PATH _PREFIX + "/classic/static/js/graph/graph_template.handlebar?v=" + BUILD _VERSION ,
2017-12-21 09:58:05 -08:00
success : function ( data ) {
graphTemplate = data ;
Mustache . parse ( data ) ;
if ( isDeprecatedGraphURL ( ) ) {
redirectToMigratedURL ( ) ;
} else {
var Page = new Prometheus . Page ( ) ;
Page . init ( ) ;
}
}
} ) ;
}
2013-01-24 04:55:32 -08:00
$ ( init ) ;