Skip to content

Instantly share code, notes, and snippets.

@clintongormley
Created April 16, 2013 10:43
Show Gist options
  • Select an option

  • Save clintongormley/5394980 to your computer and use it in GitHub Desktop.

Select an option

Save clintongormley/5394980 to your computer and use it in GitHub Desktop.

Revisions

  1. clintongormley created this gist Apr 16, 2013.
    66 changes: 66 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,66 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" />
    <title>Elasticsearch stats analyzer</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript" src="rules.js"></script>
    <script type="text/javascript" src="stats.js"></script>
    <script type="text/javascript" src="parser.js"></script>
    <script type="text/javascript">
    $(function(){
    $('#es_host').submit(function(e){
    e.preventDefault();
    analyze($('#host').val());
    });
    analyze($('#host').val());
    })
    </script>
    <style type="text/css">
    html { color: #333; font-family: sans; font-size: 90% }
    #results {padding-bottom: 300px; padding-right: 500px; display: inline-block}
    h1 { font-size: 1.8em }
    table { border-collapse: collapse; font-size: 85%; margin-left: 20px; }
    th,td { text-align: left; padding: 2px 5px; border:1px solid #eee;white-space: pre}
    th { padding-left: 15px}
    th.group {
    padding-left: 5px;
    padding-top: 20px;
    border: none;
    border-bottom: 1px solid #999;
    font-size: 110%;
    }
    td > div, th > div {position: relative;}
    .desc,.lookups { position: absolute; width: 500px;
    display: none; left: 30px; top: 40px; background: white;
    border: 2px solid #ccc; padding: 5px 10px;
    font-weight: normal; z-index: 10000;
    box-shadow: 5px 5px 5px #ddd;
    white-space: normal;
    font-size: 115%;
    }
    tr:hover { box-shadow: 5px 5px 5px #ddd; }
    th:hover .desc { display: block}
    td:hover .lookups { display: block}
    .error { font-weight: bold; color: red;}
    .red { background: #ff8484 }
    .amber { background: #ffbd7d }
    .green { background: #ddffba }
    .grey { background: #ddd}
    .lookups, .code { font-family: monospace; }
    </style>
    </head>
    <body>
    <div>
    <h1>Nodes stats analyzer</h1>
    <form id="es_host">
    <p>
    <label for="host">Hostname:</label>
    <input id="host" type="text" value="localhost:9200">
    <button type="submit">Analyze</button>
    </p>
    </form>
    </div>
    <div id="results"></div>
    </body>
    </html>
    454 changes: 454 additions & 0 deletions parser.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,454 @@
    /* Jison generated parser */
    var parser = (function(){
    var parser = {trace: function trace() { },
    yy: {},
    symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"+":6,"-":7,"*":8,"/":9,"(":10,")":11,"NUMBER":12,"KEY":13,"$accept":0,"$end":1},
    terminals_: {2:"error",5:"EOF",6:"+",7:"-",8:"*",9:"/",10:"(",11:")",12:"NUMBER",13:"KEY"},
    productions_: [0,[3,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$) {

    var $0 = $$.length - 1;
    switch (yystate) {
    case 1: return $$[$0-1];
    break;
    case 2:this.$ = $$[$0-2]+$$[$0];
    break;
    case 3:this.$ = $$[$0-2]-$$[$0];
    break;
    case 4:this.$ = $$[$0-2]*$$[$0];
    break;
    case 5:this.$ = $$[$0] ? $$[$0-2]/$$[$0] : 0;
    break;
    case 6:this.$ = $$[$0-1];
    break;
    case 7:this.$ = Number(yytext);
    break;
    case 8: this.$=yy.get_key(yytext);
    break;
    }
    },
    table: [{3:1,4:2,10:[1,3],12:[1,4],13:[1,5]},{1:[3]},{5:[1,6],6:[1,7],7:[1,8],8:[1,9],9:[1,10]},{4:11,10:[1,3],12:[1,4],13:[1,5]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],11:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],11:[2,8]},{1:[2,1]},{4:12,10:[1,3],12:[1,4],13:[1,5]},{4:13,10:[1,3],12:[1,4],13:[1,5]},{4:14,10:[1,3],12:[1,4],13:[1,5]},{4:15,10:[1,3],12:[1,4],13:[1,5]},{11:[1,16],6:[1,7],7:[1,8],8:[1,9],9:[1,10]},{6:[2,2],7:[2,2],8:[1,9],9:[1,10],5:[2,2],11:[2,2]},{6:[2,3],7:[2,3],8:[1,9],9:[1,10],5:[2,3],11:[2,3]},{6:[2,4],7:[2,4],8:[2,4],9:[2,4],5:[2,4],11:[2,4]},{6:[2,5],7:[2,5],8:[2,5],9:[2,5],5:[2,5],11:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],11:[2,6]}],
    defaultActions: {6:[2,1]},
    parseError: function parseError(str, hash) {
    throw new Error(str);
    },
    parse: function parse(input) {
    var self = this,
    stack = [0],
    vstack = [null], // semantic value stack
    lstack = [], // location stack
    table = this.table,
    yytext = '',
    yylineno = 0,
    yyleng = 0,
    recovering = 0,
    TERROR = 2,
    EOF = 1;

    //this.reductionCount = this.shiftCount = 0;

    this.lexer.setInput(input);
    this.lexer.yy = this.yy;
    this.yy.lexer = this.lexer;
    this.yy.parser = this;
    if (typeof this.lexer.yylloc == 'undefined')
    this.lexer.yylloc = {};
    var yyloc = this.lexer.yylloc;
    lstack.push(yyloc);

    var ranges = this.lexer.options && this.lexer.options.ranges;

    if (typeof this.yy.parseError === 'function')
    this.parseError = this.yy.parseError;

    function popStack (n) {
    stack.length = stack.length - 2*n;
    vstack.length = vstack.length - n;
    lstack.length = lstack.length - n;
    }

    function lex() {
    var token;
    token = self.lexer.lex() || 1; // $end = 1
    // if token isn't its numeric value, convert
    if (typeof token !== 'number') {
    token = self.symbols_[token] || token;
    }
    return token;
    }

    var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
    while (true) {
    // retreive state number from top of stack
    state = stack[stack.length-1];

    // use default actions if available
    if (this.defaultActions[state]) {
    action = this.defaultActions[state];
    } else {
    if (symbol === null || typeof symbol == 'undefined') {
    symbol = lex();
    }
    // read action for current state and first input
    action = table[state] && table[state][symbol];
    }

    // handle parse error
    _handle_error:
    if (typeof action === 'undefined' || !action.length || !action[0]) {

    var errStr = '';
    if (!recovering) {
    // Report error
    expected = [];
    for (p in table[state]) if (this.terminals_[p] && p > 2) {
    expected.push("'"+this.terminals_[p]+"'");
    }
    if (this.lexer.showPosition) {
    errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + (this.terminals_[symbol] || symbol)+ "'";
    } else {
    errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
    (symbol == 1 /*EOF*/ ? "end of input" :
    ("'"+(this.terminals_[symbol] || symbol)+"'"));
    }
    this.parseError(errStr,
    {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
    }

    // just recovered from another error
    if (recovering == 3) {
    if (symbol == EOF) {
    throw new Error(errStr || 'Parsing halted.');
    }

    // discard current lookahead and grab another
    yyleng = this.lexer.yyleng;
    yytext = this.lexer.yytext;
    yylineno = this.lexer.yylineno;
    yyloc = this.lexer.yylloc;
    symbol = lex();
    }

    // try to recover from error
    while (1) {
    // check for error recovery rule in this state
    if ((TERROR.toString()) in table[state]) {
    break;
    }
    if (state === 0) {
    throw new Error(errStr || 'Parsing halted.');
    }
    popStack(1);
    state = stack[stack.length-1];
    }

    preErrorSymbol = symbol == 2 ? null : symbol; // save the lookahead token
    symbol = TERROR; // insert generic error symbol as new lookahead
    state = stack[stack.length-1];
    action = table[state] && table[state][TERROR];
    recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
    }

    // this shouldn't happen, unless resolve defaults are off
    if (action[0] instanceof Array && action.length > 1) {
    throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
    }

    switch (action[0]) {

    case 1: // shift
    //this.shiftCount++;

    stack.push(symbol);
    vstack.push(this.lexer.yytext);
    lstack.push(this.lexer.yylloc);
    stack.push(action[1]); // push state
    symbol = null;
    if (!preErrorSymbol) { // normal execution/no error
    yyleng = this.lexer.yyleng;
    yytext = this.lexer.yytext;
    yylineno = this.lexer.yylineno;
    yyloc = this.lexer.yylloc;
    if (recovering > 0)
    recovering--;
    } else { // error just occurred, resume old lookahead f/ before error
    symbol = preErrorSymbol;
    preErrorSymbol = null;
    }
    break;

    case 2: // reduce
    //this.reductionCount++;

    len = this.productions_[action[1]][1];

    // perform semantic action
    yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
    // default location, uses first token for firsts, last for lasts
    yyval._$ = {
    first_line: lstack[lstack.length-(len||1)].first_line,
    last_line: lstack[lstack.length-1].last_line,
    first_column: lstack[lstack.length-(len||1)].first_column,
    last_column: lstack[lstack.length-1].last_column
    };
    if (ranges) {
    yyval._$.range = [lstack[lstack.length-(len||1)].range[0], lstack[lstack.length-1].range[1]];
    }
    r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);

    if (typeof r !== 'undefined') {
    return r;
    }

    // pop off stack
    if (len) {
    stack = stack.slice(0,-1*len*2);
    vstack = vstack.slice(0, -1*len);
    lstack = lstack.slice(0, -1*len);
    }

    stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
    vstack.push(yyval.$);
    lstack.push(yyval._$);
    // goto new state = table[STATE][NONTERMINAL]
    newState = table[stack[stack.length-2]][stack[stack.length-1]];
    stack.push(newState);
    break;

    case 3: // accept
    return true;
    }

    }

    return true;
    }};
    /* Jison generated lexer */
    var lexer = (function(){
    var lexer = ({EOF:1,
    parseError:function parseError(str, hash) {
    if (this.yy.parser) {
    this.yy.parser.parseError(str, hash);
    } else {
    throw new Error(str);
    }
    },
    setInput:function (input) {
    this._input = input;
    this._more = this._less = this.done = false;
    this.yylineno = this.yyleng = 0;
    this.yytext = this.matched = this.match = '';
    this.conditionStack = ['INITIAL'];
    this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
    if (this.options.ranges) this.yylloc.range = [0,0];
    this.offset = 0;
    return this;
    },
    input:function () {
    var ch = this._input[0];
    this.yytext += ch;
    this.yyleng++;
    this.offset++;
    this.match += ch;
    this.matched += ch;
    var lines = ch.match(/(?:\r\n?|\n).*/g);
    if (lines) {
    this.yylineno++;
    this.yylloc.last_line++;
    } else {
    this.yylloc.last_column++;
    }
    if (this.options.ranges) this.yylloc.range[1]++;

    this._input = this._input.slice(1);
    return ch;
    },
    unput:function (ch) {
    var len = ch.length;
    var lines = ch.split(/(?:\r\n?|\n)/g);

    this._input = ch + this._input;
    this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
    //this.yyleng -= len;
    this.offset -= len;
    var oldLines = this.match.split(/(?:\r\n?|\n)/g);
    this.match = this.match.substr(0, this.match.length-1);
    this.matched = this.matched.substr(0, this.matched.length-1);

    if (lines.length-1) this.yylineno -= lines.length-1;
    var r = this.yylloc.range;

    this.yylloc = {first_line: this.yylloc.first_line,
    last_line: this.yylineno+1,
    first_column: this.yylloc.first_column,
    last_column: lines ?
    (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
    this.yylloc.first_column - len
    };

    if (this.options.ranges) {
    this.yylloc.range = [r[0], r[0] + this.yyleng - len];
    }
    return this;
    },
    more:function () {
    this._more = true;
    return this;
    },
    less:function (n) {
    this.unput(this.match.slice(n));
    },
    pastInput:function () {
    var past = this.matched.substr(0, this.matched.length - this.match.length);
    return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
    },
    upcomingInput:function () {
    var next = this.match;
    if (next.length < 20) {
    next += this._input.substr(0, 20-next.length);
    }
    return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
    },
    showPosition:function () {
    var pre = this.pastInput();
    var c = new Array(pre.length + 1).join("-");
    return pre + this.upcomingInput() + "\n" + c+"^";
    },
    next:function () {
    if (this.done) {
    return this.EOF;
    }
    if (!this._input) this.done = true;

    var token,
    match,
    tempMatch,
    index,
    col,
    lines;
    if (!this._more) {
    this.yytext = '';
    this.match = '';
    }
    var rules = this._currentRules();
    for (var i=0;i < rules.length; i++) {
    tempMatch = this._input.match(this.rules[rules[i]]);
    if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
    match = tempMatch;
    index = i;
    if (!this.options.flex) break;
    }
    }
    if (match) {
    lines = match[0].match(/(?:\r\n?|\n).*/g);
    if (lines) this.yylineno += lines.length;
    this.yylloc = {first_line: this.yylloc.last_line,
    last_line: this.yylineno+1,
    first_column: this.yylloc.last_column,
    last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
    this.yytext += match[0];
    this.match += match[0];
    this.matches = match;
    this.yyleng = this.yytext.length;
    if (this.options.ranges) {
    this.yylloc.range = [this.offset, this.offset += this.yyleng];
    }
    this._more = false;
    this._input = this._input.slice(match[0].length);
    this.matched += match[0];
    token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
    if (this.done && this._input) this.done = false;
    if (token) return token;
    else return;
    }
    if (this._input === "") {
    return this.EOF;
    } else {
    return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
    {text: "", token: null, line: this.yylineno});
    }
    },
    lex:function lex() {
    var r = this.next();
    if (typeof r !== 'undefined') {
    return r;
    } else {
    return this.lex();
    }
    },
    begin:function begin(condition) {
    this.conditionStack.push(condition);
    },
    popState:function popState() {
    return this.conditionStack.pop();
    },
    _currentRules:function _currentRules() {
    return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
    },
    topState:function () {
    return this.conditionStack[this.conditionStack.length-2];
    },
    pushState:function begin(condition) {
    this.begin(condition);
    }});
    lexer.options = {};
    lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START) {

    var YYSTATE=YY_START
    switch($avoiding_name_collisions) {
    case 0:/* skip whitespace */
    break;
    case 1:return 13
    break;
    case 2:return 12
    break;
    case 3:return 8
    break;
    case 4:return 9
    break;
    case 5:return 7
    break;
    case 6:return 6
    break;
    case 7:return '^'
    break;
    case 8:return '!'
    break;
    case 9:return '%'
    break;
    case 10:return 10
    break;
    case 11:return 11
    break;
    case 12:return 5
    break;
    case 13:return 'INVALID'
    break;
    }
    };
    lexer.rules = [/^(?:\s+)/,/^(?:[a-z]([a-zA-Z_.]|\\\s)+)/,/^(?:[0-9]+(\.[0-9]+)?\b)/,/^(?:\*)/,/^(?:\/)/,/^(?:-)/,/^(?:\+)/,/^(?:\^)/,/^(?:!)/,/^(?:%)/,/^(?:\()/,/^(?:\))/,/^(?:$)/,/^(?:.)/];
    lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}};
    return lexer;})()
    parser.lexer = lexer;
    function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
    return new Parser;
    })();
    if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
    exports.parser = parser;
    exports.Parser = parser.Parser;
    exports.parse = function () { return parser.parse.apply(parser, arguments); }
    exports.main = function commonjsMain(args) {
    if (!args[1])
    throw new Error('Usage: '+args[0]+' FILE');
    var source, cwd;
    if (typeof process !== 'undefined') {
    source = require("fs").readFileSync(require("path").resolve(args[1]), "utf8");
    } else {
    source = require("file").path(require("file").cwd()).join(args[1]).read({charset: "utf-8"});
    }
    return exports.parser.parse(source);
    }
    if (typeof module !== 'undefined' && require.main === module) {
    exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
    }
    }
    304 changes: 304 additions & 0 deletions rules.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,304 @@
    var Rules = [
    "General", general_rules(),
    "Filesystem", fs_rules(),
    "Actions", action_rules(),
    "Cache", cache_rules(),
    "Memory", memory_rules(),
    // "Threads", thread_rules(),
    "Network", network_rules()
    ];

    function general_rules() {
    return [
    {
    "Name" : {
    "val" : "stats.name"
    }
    },
    {
    "IP" : {
    "val" : "stats.transport_address"
    }
    },
    {
    "ID" : {
    "val" : "id"
    }
    },
    {
    "ES Uptime" : {
    "unit" : "days",
    "format" : "float",
    "val" : "stats.jvm.uptime_in_millis / 1000 / 60 / 60 / 24"
    }
    },
    {
    "CPU": {
    "val": "info.os.cpu.model"
    }
    },
    {
    "Cores": {
    "val": "info.os.cpu.total_cores"
    }
    }
    ];
    }

    function fs_rules() {
    return [
    {
    "Store size" : {
    "val" : "stats.indices.store.size"
    }
    },
    {
    "Docs total" : {
    "format" : "comma",
    "val" : "stats.indices.docs.count"
    }
    },
    {
    "Docs deleted %" : {
    "comment" : "High values indicate insufficient merging. Slow I/O?",
    "format" : "pct",
    "val" : "stats.indices.docs.deleted / stats.indices.docs.count",
    "upper_limit" : [ "0.1", "0.25" ]
    }
    },
    {
    "Merge size" : {
    "val" : "stats.indices.merges.total_size"
    }
    },
    {
    "Merge time" : {
    "val" : "stats.indices.merges.total_time"
    }
    },
    {
    "Merge rate" : {
    "unit" : "MB/s",
    "comment" : "Low rates indicate throttling or slow I/O",
    "format" : "float",
    "val" : "stats.indices.merges.total_size_in_bytes / stats.indices.merges.total_time_in_millis / 1000"
    }
    },
    {
    "File descriptors" : {
    "format" : "comma",
    "val" : "stats.process.open_file_descriptors"
    }
    }
    ];
    }

    function action_rules() {
    return [
    {
    "Indexing - index" : {
    "comment" : "High values indicate complex documents or slow I/O or CPU.",
    "format" : "ms",
    "val" : "stats.indices.indexing.index_time_in_millis / stats.indices.indexing.index_total",
    "upper_limit" : [ "10", "50" ]
    }
    },
    {
    "Indexing - delete" : {
    "comment" : "High values indicate slow I/O.",
    "format" : "ms",
    "val" : "stats.indices.indexing.delete_time_in_millis / stats.indices.indexing.delete_total",
    "upper_limit" : [ "5", "10" ]
    }
    },
    {
    "Search - query" : {
    "comment" : "High values indicate complex or inefficient queries, insufficient use of filters, insufficient RAM for caching, slow I/O or CPU.",
    "format" : "ms",
    "val" : "stats.indices.search.query_time_in_millis / stats.indices.search.query_total",
    "upper_limit" : [ "50", "500" ]
    }
    },
    {
    "Search - fetch" : {
    "comment" : "High values indicate slow I/O, large docs, or fetching too many docs, eg deep paging.",
    "format" : "ms",
    "val" : "stats.indices.search.fetch_time_in_millis / stats.indices.search.fetch_total",
    "upper_limit" : [ "8", "15" ]
    }
    },
    {
    "Get - total" : {
    "comment" : "High values indicate slow I/O.",
    "format" : "ms",
    "val" : "stats.indices.get.time_in_millis / stats.indices.get.total",
    "upper_limit" : [ "5", "10" ]
    }
    },
    {
    "Get - exists" : {
    "comment" : "???",
    "format" : "ms",
    "val" : "stats.indices.get.exists_time_in_millis / stats.indices.get.exists_total",
    "upper_limit" : [ "5", "10" ]
    }
    },
    {
    "Get - missing" : {
    "comment" : "???",
    "format" : "ms",
    "val" : "stats.indices.get.missing_time_in_millis / stats.indices.get.missing_total",
    "upper_limit" : [ "2", "5" ]
    }
    },
    {
    "Refresh" : {
    "comment" : "High values indicate slow I/O.",
    "format" : "ms",
    "val" : "stats.indices.refresh.total_time_in_millis / stats.indices.refresh.total",
    "upper_limit" : [ "10", "20" ]
    }
    },
    {
    "Flush" : {
    "comment" : "High values indicate slow I/O.",
    "format" : "ms",
    "val" : "stats.indices.flush.total_time_in_millis / stats.indices.flush.total",
    "upper_limit" : [ "750", "1500" ]
    }
    }
    ];
    }

    function cache_rules() {
    return [
    {
    "Field size" : {
    "val" : "stats.indices.fielddata.memory_size"
    }
    },
    {
    "Field evictions" : {
    "comment" : "Field values should not be evicted - insufficient RAM for current queries.",
    "format" : "comma",
    "val" : "stats.indices.fielddata.evictions",
    "upper_limit" : [ "0", "0" ]
    }
    },
    {
    "Filter size" : {
    "val" : "stats.indices.cache.filter_size"
    }
    },
    {
    "Filter evictions" : {
    "unit" : "per query",
    "comment" : "High values indicate insufficient RAM for current queries, or frequent use of one-off values in filters.",
    "format" : "float",
    "val" : "stats.indices.cache.filter_evictions / stats.indices.search.query_total",
    "upper_limit" : [ "0.1", "0.2" ]
    }
    },
    {
    "ID size" : {
    "val" : "stats.indices.cache.id_cache_size"
    }
    },
    {
    "ID %" : {
    "val" : "stats.indices.cache.id_cache_size_in_bytes / stats.jvm.mem.heap_committed_in_bytes",
    "format" : "pct",
    "upper_limit": ["0.2","0.4"],
    "comment": "Large parent/child ID caches reduce the amount of memory available on the heap."
    }
    }
    ];
    }

    function memory_rules() {
    return [
    {
    "Total mem" : {
    "unit" : "gb",
    "format" : "comma",
    "val" : "( stats.os.mem.actual_used_in_bytes + stats.os.mem.actual_free_in_bytes ) / 1024 / 1024 / 1024"
    }
    },
    {
    "Heap size" : {
    "unit" : "gb",
    "comment" : "A heap size over 32GB causes the JVM to use uncompressed pointers and can slow GC.",
    "format" : "float",
    "val" : "stats.jvm.mem.heap_committed_in_bytes / 1024 / 1024 / 1024",
    "upper_limit" : [ "30", "32" ]
    }
    },
    {
    "Heap % of RAM" : {
    "comment" : "Approx 40-50% of RAM should be available to the kernel for file caching.",
    "format" : "pct",
    "val" : "stats.jvm.mem.heap_committed_in_bytes / (stats.os.mem.actual_used_in_bytes + stats.os.mem.actual_free_in_bytes)",
    "upper_limit" : [ "0.6", "0.75" ]
    }
    },
    {
    "Heap used %" : {
    "format" : "pct",
    "val" : "stats.jvm.mem.heap_used_in_bytes / stats.jvm.mem.heap_committed_in_bytes",
    }
    },
    {
    "GC MarkSweep frequency" : {
    "unit" : "s",
    "comment" : "Too frequent GC indicates memory pressure and need for more heap space.",
    "format" : "comma",
    "val" : "stats.jvm.uptime_in_millis / stats.jvm.gc.collectors.ConcurrentMarkSweep.collection_count / 1000",
    "lower_limit" : [ "30", "15", "0" ]
    }
    },
    {
    "GC MarkSweep duration" : {
    "comment" : "Long durations may indicate that swapping is slowing down GC, or need for more heap space.",
    "format" : "ms",
    "val" : "stats.jvm.gc.collectors.ConcurrentMarkSweep.collection_time_in_millis / stats.jvm.gc.collectors.ConcurrentMarkSweep.collection_count",
    "upper_limit" : [ "150", "400" ]
    }
    },
    {
    "GC ParNew frequency" : {
    "unit" : "s",
    "format" : "comma",
    "val" : "stats.jvm.uptime_in_millis / stats.jvm.gc.collectors.ParNew.collection_count / 1000"
    }
    },
    {
    "GC ParNew duration" : {
    "format" : "ms",
    "val" : "stats.jvm.gc.collectors.ParNew.collection_time_in_millis / stats.jvm.gc.collectors.ParNew.collection_count",
    "upper_limit" : [ "100", "200" ]
    }
    },
    {
    "Swap" : {
    "val": "stats.os.swap.used_in_bytes / 1024 / 1024",
    "unit": "mb",
    "upper_limit": ["1","1"],
    "comment": "Any use of swap by the JVM, no matter how small, can greatly impact the speed of the garbage collector."
    }
    }
    ];
    }

    function network_rules() {
    return [
    {
    "HTTP connection rate" : {
    "unit" : "per sec",
    "comment" : "Too many HTTP connection per second may exhaust the number of sockets available in the kernel, and cause a service outage.",
    "format" : "comma",
    "val" : "stats.http.total_opened / stats.jvm.uptime_in_millis * 1000",
    "upper_limit" : [ "5", "30" ]
    }
    }
    ];
    }
    247 changes: 247 additions & 0 deletions stats.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,247 @@
    var Rules;

    var Formats = {
    comma : function(n) {
    n = Math.round(n) + '';
    var re = /^([-+]?\d+)(\d{3})/;
    while (1) {
    var new_n = n.replace(re, '$1,$2');
    if (new_n === n) {
    break;
    }
    n = new_n
    }
    return n;
    },
    pct : function(n) {
    n = Math.round(n * 1000) / 10;
    return n + '%';
    },
    ms : function(n) {
    n = Math.round(n * 100) / 100;
    return n + 'ms';
    },
    float : function(n) {
    return Math.round(n * 10) / 10;
    },
    };

    var lookups = {};

    function init_parser(top_data) {

    return function(key) {
    var data = top_data;
    key = key.replace(/\\/g, '');
    var parts = key.split('.');
    var last = parts.pop();
    for (i = 0; i < parts.length; i++) {
    var part = parts[i];
    if (!typeof data[part] === 'object') {
    throw new Error("Invalid key: " + key)
    }
    data = data[part];
    }
    if (data === undefined || data[last] === undefined) {
    throw new Error("Invalid key: " + key)
    }
    lookups[key] = data[last];
    return data[last];
    }
    }

    function rule_lookups() {
    var temp = [];
    var keys = Object.keys(lookups).sort();
    for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    temp.push([key,lookups[key]]);
    }
    return temp;
    }

    function reset_parser() {
    for (var key in lookups) {
    delete lookups[key];
    }
    }

    function es_request(host, uri) {
    return $.ajax({
    url : 'http://' + host + uri,
    dataType : "jsonp",
    timeout: 2000
    });
    }

    function analyze(host) {
    var host = $('#host').val();
    var data = {
    nodes : {}
    };

    set_contents('<h1>Fetching...</h1>');
    es_request(host, '/_cluster/nodes/stats?all=1').done(function(nstats) {
    es_request(host, "/_nodes?all=1").done(function(ninfo) {
    data.cluster_name = nstats.cluster_name;
    for (node in nstats.nodes) {
    data.nodes[node] = {
    stats : nstats.nodes[node],
    info : ninfo.nodes[node]
    };
    }
    calc_stats(data);
    })
    }).fail(function() {
    set_contents('<h1>Failed to retrieve data from: ' +escape(host)+'</h1>');
    });

    }

    function calc_stats(data) {
    var stats = [];
    var lookups = {};

    for (id in data.nodes) {
    var node = data.nodes[id];
    parser.yy.get_key = init_parser(node,lookups);

    node.id = id;
    node.stats.transport_address =
    node.stats.transport_address.replace(/inet\[\/([^\]]+)\]/, "$1");

    var entry = [];
    for (var i = 1; i < Rules.length; i=i+2) {
    Rules[i].forEach(function(el) {
    reset_parser();
    for (title in el) {
    var rule = el[title];
    try {
    var val = parser.parse(rule.val);
    var color = choose_color(rule, val);
    if (rule.format) {
    val = Formats[rule.format](val)
    }
    if (rule.unit) {
    val = val + ' ' + rule.unit
    }
    var lookups = rule_lookups();
    entry.push([ val, color,lookups ]);

    } catch (e) {
    entry.push([ '?', 'grey',[['Error',e+'']] ]);
    }
    }
    });
    }
    stats.push(entry);
    }
    generate_table(data.cluster_name, stats);
    }

    function choose_color(rule, val) {
    if (rule.upper_limit) {
    return val <= rule.upper_limit[0] ? 'green'
    : val <= rule.upper_limit[1] ? 'amber' : 'red';
    }
    if (rule.lower_limit) {
    if (rule.lower_limit.length === 3 && val === 0) {
    return 'green';
    }
    return val >= rule.lower_limit[0] ? 'green'
    : val >= rule.lower_limit[1] ? 'amber' : 'red';
    }
    return ''
    }

    function generate_table(cluster_name, stats) {
    stats.sort(function(a, b) {
    if (a[0][0] < b[0][0]) {
    return -1
    }
    if (a[0][0] > b[0][0]) {
    return 1
    }
    return 0
    });

    var table = '';
    var cols = stats.length + 1;

    for (var i = 0; i < Rules.length-1; i=i+2) {
    var group_title = Rules[i];
    var rules = Rules[i+1];
    table = table + '<tr><th class="group" colspan="' + cols + '">' + escape(group_title) + ':</th></tr>';
    rules.forEach(function(el) {
    for (title in el) {
    var rule = el[title];
    var limits, sign;
    if (rule.upper_limit) {
    sign = '&gt;';
    limits = rule.upper_limit;
    } else if (rule.lower_limit) {
    sign = '&lt;';
    limits = rule.lower_limit
    }

    var comment = rule.comment;

    var row = title_cell(title, rule.val, limits, sign, rule.comment);
    stats.forEach(function(col) {
    var cell = col.shift();
    var lookups = format_lookups(cell[2]);
    row = row + '<td class="' + cell[1] + '">' + cell[0] + lookups + '</td>'
    });
    table = table + '<tr>' + row + '</tr>';
    }

    });
    }

    table = '<table cellspacing="0">' + table + '</table>';

    var date = new Date();
    var timestamp = '<p>Created on: ' + date.toLocaleString() + '</p>';
    set_contents('<h1>Cluster: ' + escape(cluster_name) + '</h1>' + timestamp + table);

    }

    function set_contents(html) {
    $('#results').html(html);
    }

    function title_cell(title, val, limits, sign, comment) {
    title = escape(title);
    val = escape(val);
    comment = comment ? '<p>' + escape(comment) + '</p>' : '';
    var limit = '';
    if (limits) {
    limit =
    '<p><b>Limits:</b></p>' + '<p class="code">Amber: ' + sign + ' ' +
    limits[0] + '</p>' + '<p class="code">Red:&nbsp;&nbsp; ' +
    sign + ' ' + limits[1] + '</p>'
    }
    return '<th>' + '<div>' + title + '<div class="desc">' + '<p><b>' + title +
    '</p></b>' + '<p class="code">' + val + '</p>' + limit + comment +
    '</div>' + '</div>' + '</th>'
    }

    function format_lookups(vals) {
    if (!vals) { return ''};
    var rows = [];
    for (var i = 0; i < vals.length; i++) {
    var key = vals[i][0];
    var val = Formats.comma(vals[i][1]);
    if (val == "NaN") { val = vals[i][1]}
    rows.push('<b>'+escape(key)+'</b>: '+escape(val));
    }
    return '<div class="lookups_wrapper">'+
    '<div class="lookups">' +
    rows.join("<br />") +
    '</div></div>';
    }

    function escape(str) {
    return String(str).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(
    /'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }