查看响应时间在不同区间内占比的是非常常见的一个监控和 SLA 需求。Elasticsearch 对此有直接的接口支持。考虑到 kibana3 内大多数还是用 facet 接口,这里也沿用:http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-range-facet.html。
range facet 本身的使用非常简单,就像官网示例那样,直接 curl 命令就可以完成调试:
curl -XPOST http://localhost:9200/logstash-2014.08.18/_search?pretty=1 -d '{
"query" : {
"match_all" : {}
},
"facets" : {
"range1" : {
"range" : {
"field" : "resp_ms",
"ranges" : [
{ "to" : 100 },
{ "from" : 101, "to" : 500 },
{ "from" : 500 }
]
}
}
}
}'
不过在 kibana 里,我们就不要再自己拼 JSON 发请求了。elastic.js 关于 range facet 的文档见:http://docs.fullscale.co/elasticjs/ejs.RangeFacet.html
因为 range facet 本身比较简单,所以 RangeFacet 对象支持的方法也比较少。一个 addRange
方法添加 ranges 数组,一个 field
方法添加 field 名称即可。
代码实现难点
面板代码的主要层次和方法,在上节中已经讲过。在二次开发中,完全可以复制一个类似的现有 panel ,然后开始编辑。比如本节预备开发一个以饼图展示区间统计的面板,即可复制 terms 面板代码:
# cp -r src/app/panels/terms src/app/panels/ranges
# sed -i 's/terms/ranges/g' src/app/panels/ranges/*
terms 面板中,设计有 fmode 和 tmode ,分别控制 terms 和 term_stats 时的参数情况,而 ranges 面板中并不需要,所以去除这部分属性,在 module.js
中,有关 tmode 的内容更加简单。
构建请求
if($scope.panel.tmode === 'ranges') {
rangefacet = $scope.ejs.RangeFacet('ranges');
// AddRange
_.each($scope.panel.values, function(v) {
rangefacet.addRange(v.from, v.to);
});
request = request
.facet(rangefacet
.field($scope.field)
.facetFilter($scope.ejs.QueryFilter(
$scope.ejs.FilteredQuery(
boolQuery,
filterSrv.getBoolFilter(filterSrv.ids())
)))).size(0);
}
组织结果
function build_results() {
var k = 0;
scope.data = [];
_.each(scope.results.facets.ranges.ranges, function(v) {
var slice;
if(scope.panel.tmode === 'ranges') {
slice = { label : [v.from,v.to], data : [[k,v.count]], actions: true};
}
scope.data.push(slice);
k = k + 1;
});
scope.data.push({label:'Missing field',
data:[[k,scope.results.facets.ranges.missing]],meta:"missing",color:'#aaa',opacity:0});
if(scope.panel.tmode === 'ranges') {
scope.data.push({label:'Other values',
data:[[k+1,scope.results.facets.ranges.other]],meta:"other",color:'#444'});
}
}
区间范围配置编辑
这个新 panel 的实现,更复杂的地方在配置编辑上如何让 range 范围值支持自定义添加和填写。对此,设计有一个 $scope.panel.values
数组,对应每个区间:
values
用于计算 facet 的数值范围数组。数组每个元素包括:
editor.html
里 Field 栏由普通的文本输入框改成表格输入:
<div class="editor-option">
<label class="small">Field</label>
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.field" ng-change="set_refresh(true)">
</div>
<div class="editor-row">
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>From</th>
<th>To</th>
<th ng-show="panel.values.length > 1">Delete</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="value in panel.values">
<td>
<div class="editor-option">
<input class="input-small" type="number" ng-model="value.from" ng-change="set_refresh(true)">
</div>
</td>
<td>
<div class="editor-option">
<input class="input-small" type="number" ng-model="value.to" ng-change="set_refresh(true)">
</div>
</td>
<td ng-show="panel.values.length > 1">
<i ng-click="panel.values = _.without(panel.values, value);set_refresh(true)" class="pointer icon-remove"></i>
</td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-success" ng-click="add_new_value(panel);set_refresh(true)"><i class="icon-plus-sign"></i> Add value</button>
</div>
</div>
这里使用了一个 add_new_value
函数,需要在 module.js
中定义:
$scope.defaultValue = {
'from': 0,
'to' : 100
};
$scope.add_new_value = function(panel) {
panel.values.push(angular.copy($scope.defaultValue));
};
面板单击生成的 filtering 条件
另一个需要注意的地方是饼图出来以后,单击饼图区域,自动生成的 filterSrv
内容。一般的面板这里都是 terms
类型的 filterSrv
,传递的是面板的 label 值。而我们这里 label 值显然不是 ES 有效的 terms 语法,还好 filterSrv
有 range
类型(histogram 面板的 time
类型的 filterSrv
是在 daterange 基础上实现的),所以稍微修改就可以了。
$scope.build_search = function(range,negate) {
if(_.isUndefined(range.meta)) {
filterSrv.set({type:'range',field:$scope.field,from:range.label[0],to:range.label[1],
mandate:(negate ? 'mustNot':'must')});
} else if(range.meta === 'missing') {
filterSrv.set({type:'exists',field:$scope.field,
mandate:(negate ? 'must':'mustNot')});
} else {
return;
}
};
面板效果
最终效果如下:
属性编辑界面效果如下:
ranges panel 完整代码,见 https://github.com/chenryn/kibana/src/app/panels/ranges。