Skip to content

Commit f295d76

Browse files
fhojml
authored andcommitted
Add support for Elasticsearch datasource #99 (#100)
* add elasticsearch Target class and helpers Add an ElasticsearchTarget and query related helper classes to create graphs with queries from an elasticsearch datasource. This commit only adds partial support for elasticsearch queries. Grafana supports a lot more Metric and Aggregators for elasticsearch. * docs: add an elasticsearch dashboard example * elasticsearch: adapt implementation to review comments - add documentation - improve naming - remove Settings objects, move the fields to the aggregator objects - make fields that don't change constants in to_json_data() Thanks @fho!
1 parent e4bab5f commit f295d76

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
This is an exemplary Grafana board that uses an elasticsearch datasource.
3+
4+
The graph shows the following metrics for HTTP requests to the URL path "/login":
5+
- number of successful requests resulted in a HTTP response code between 200-300
6+
- number of failed requests resulted in a HTTP response code between 400-500,
7+
- Max. response time per point of time of HTTP requests
8+
"""
9+
10+
from grafanalib.core import *
11+
from grafanalib.elasticsearch import *
12+
13+
suc_label = "Success (200-300)"
14+
clt_err_label = "Client Errors (400-500)"
15+
resptime_label = "Max response time"
16+
17+
filters = [
18+
Filter(query="response: [200 TO 300]", label=suc_label),
19+
Filter(query="response: [400 TO 500]", label=clt_err_label),
20+
]
21+
22+
tgts = [
23+
ElasticsearchTarget(
24+
query='request: "/login"',
25+
bucketAggs=[
26+
FiltersGroupBy(filters=filters),
27+
DateHistogramGroupBy(interval="10m")],
28+
).auto_bucket_agg_ids(),
29+
ElasticsearchTarget(
30+
query='request: "/login"',
31+
metricAggs=[MaxMetricAgg(field="resptime")],
32+
alias=resptime_label,
33+
).auto_bucket_agg_ids(),
34+
]
35+
36+
g = Graph(
37+
title="login requests",
38+
dataSource="elasticsearch",
39+
targets=tgts,
40+
lines=False,
41+
legend=Legend(alignAsTable=True, rightSide=True, total=True, current=True, max=True),
42+
lineWidth=1,
43+
nullPointMode=NULL_AS_NULL,
44+
seriesOverrides=[
45+
{
46+
"alias": suc_label,
47+
"bars": True,
48+
"lines": False,
49+
"stack": "A",
50+
"yaxis": 1,
51+
"color": "#629E51"
52+
},
53+
{
54+
"alias": clt_err_label,
55+
"bars": True,
56+
"lines": False,
57+
"stack": "A",
58+
"yaxis": 1,
59+
"color": "#E5AC0E"
60+
},
61+
{
62+
"alias": resptime_label,
63+
"lines": True,
64+
"fill": 0,
65+
"nullPointMode": "connected",
66+
"steppedLine": True,
67+
"yaxis": 2,
68+
"color": "#447EBC"
69+
},
70+
],
71+
yAxes=[
72+
YAxis(
73+
label="Count",
74+
format=SHORT_FORMAT,
75+
decimals=0
76+
),
77+
YAxis(
78+
label="Response Time",
79+
format=SECONDS_FORMAT,
80+
decimals=2
81+
),
82+
],
83+
transparent=True,
84+
span=12,
85+
)
86+
87+
dashboard = Dashboard(title="HTTP dashboard", rows=[Row(panels=[g])])

grafanalib/elasticsearch.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"""Helpers to create Elasticsearch-specific Grafana queries."""
2+
3+
import attr
4+
import itertools
5+
from attr.validators import instance_of
6+
7+
DATE_HISTOGRAM_DEFAULT_FIELD = "time_iso8601"
8+
ORDER_ASC = "asc"
9+
ORDER_DESC = "desc"
10+
11+
12+
@attr.s
13+
class CountMetricAgg(object):
14+
"""An aggregator that counts the number of values.
15+
16+
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html
17+
18+
It's the default aggregator for elasticsearch queries.
19+
"""
20+
def to_json_data(self):
21+
return {
22+
'type': 'count',
23+
'field': 'select field',
24+
'settings': {},
25+
}
26+
27+
28+
@attr.s
29+
class MaxMetricAgg(object):
30+
"""An aggregator that provides the max. value among the values.
31+
32+
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html
33+
34+
:param field: name of elasticsearch field to provide the maximum for
35+
"""
36+
field = attr.ib(default="", validator=instance_of(str))
37+
38+
def to_json_data(self):
39+
return {
40+
'type': 'max',
41+
'field': self.field,
42+
'settings': {},
43+
}
44+
45+
46+
@attr.s
47+
class DateHistogramGroupBy(object):
48+
"""A bucket aggregator that groups results by date.
49+
50+
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html
51+
52+
:param id: ascending unique number per GroupBy clause
53+
:param field: name of the elasticsearch field to group by
54+
:param interval: interval to group by
55+
:param minDocCount: min. amount of records in the timespan to return a
56+
result
57+
"""
58+
id = attr.ib(default=0, validator=instance_of(int))
59+
field = attr.ib(default=DATE_HISTOGRAM_DEFAULT_FIELD,
60+
validator=instance_of(str))
61+
interval = attr.ib(default="auto", validator=instance_of(str))
62+
minDocCount = attr.ib(default=0, validator=instance_of(int))
63+
64+
def to_json_data(self):
65+
return {
66+
'field': self.field,
67+
'id': str(self.id),
68+
'settings': {
69+
'interval': self.interval,
70+
'min_doc_count': self.minDocCount,
71+
'trimEdges': 0,
72+
},
73+
'type': 'date_histogram',
74+
}
75+
76+
77+
@attr.s
78+
class Filter(object):
79+
""" A Filter for a FilterGroupBy aggregator.
80+
81+
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html
82+
83+
:param label: label for the metric that is shown in the graph
84+
:param query: the query to filter by
85+
"""
86+
label = attr.ib(default="", validator=instance_of(str))
87+
query = attr.ib(default="", validator=instance_of(str))
88+
89+
def to_json_data(self):
90+
return {
91+
'label': self.label,
92+
'query': self.query,
93+
}
94+
95+
96+
@attr.s
97+
class FiltersGroupBy(object):
98+
""" A bucket aggregator that groups records by a filter expression.
99+
100+
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html
101+
102+
:param id: ascending unique number per GroupBy clause
103+
:param filters: list of Filter objects
104+
"""
105+
id = attr.ib(default=0, validator=instance_of(int))
106+
filters = attr.ib(default=attr.Factory(list))
107+
108+
def to_json_data(self):
109+
return {
110+
'id': str(self.id),
111+
'settings': {
112+
'filters': self.filters,
113+
},
114+
'type': 'filters',
115+
}
116+
117+
118+
@attr.s
119+
class TermsGroupBy(object):
120+
""" A multi-bucket aggregator based on field values.
121+
122+
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html
123+
124+
:param id: ascending unique number per GroupBy clause
125+
:param field: name of the field to group by
126+
:param minDocCount: min. amount of matching records to return a result
127+
:param order: ORDER_ASC or ORDER_DESC
128+
:param orderBy: term to order the bucker
129+
:param size: how many buckets are returned
130+
"""
131+
field = attr.ib(validator=instance_of(str))
132+
id = attr.ib(default=0, validator=instance_of(int))
133+
minDocCount = attr.ib(default=1, validator=instance_of(int))
134+
order = attr.ib(default=ORDER_DESC, validator=instance_of(str))
135+
orderBy = attr.ib(default="_term", validator=instance_of(str))
136+
size = attr.ib(default=0, validator=instance_of(int))
137+
138+
def to_json_data(self):
139+
return {
140+
'id': str(self.id),
141+
'type': 'terms',
142+
'field': self.field,
143+
'settings': {
144+
'min_doc_count': self.minDocCount,
145+
'order': self.order,
146+
'order_by': self.orderBy,
147+
'size': self.size,
148+
},
149+
}
150+
151+
152+
@attr.s
153+
class ElasticsearchTarget(object):
154+
"""Generates Elasticsearch target JSON structure.
155+
156+
Grafana docs on using Elasticsearch:
157+
http://docs.grafana.org/features/datasources/elasticsearch/
158+
Elasticsearch docs on querying or reading data:
159+
https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
160+
161+
:param alias: legend alias
162+
:param bucketAggs: Bucket aggregators
163+
:param metricAggs: Metric Aggregators
164+
:param query: query
165+
:param refId: target reference id
166+
"""
167+
168+
alias = attr.ib(default=None)
169+
bucketAggs = attr.ib(default=attr.Factory(
170+
lambda: [DateHistogramGroupBy()]))
171+
metricAggs = attr.ib(default=attr.Factory(lambda: [CountMetricAgg()]))
172+
query = attr.ib(default="", validator=instance_of(str))
173+
refId = attr.ib(default="", validator=instance_of(str))
174+
175+
def _map_bucket_aggs(self, f):
176+
return attr.assoc(self, bucketAggs=list(map(f, self.bucketAggs)))
177+
178+
def auto_bucket_agg_ids(self):
179+
"""Give unique IDs all bucketAggs without ID.
180+
181+
Returns a new ``ElasticsearchTarget`` that is the same as this one,
182+
except all of the bucketAggs have their ``id`` property set. Any panels
183+
which had an ``id`` property set will keep that property, all others
184+
will have auto-generated IDs provided for them.
185+
186+
If the bucketAggs don't have unique ID associated with it, the
187+
generated graph will be broken.
188+
"""
189+
ids = set([agg.id for agg in self.bucketAggs if agg.id])
190+
auto_ids = (i for i in itertools.count(1) if i not in ids)
191+
192+
def set_id(agg):
193+
if agg.id:
194+
return agg
195+
196+
return attr.evolve(agg, id=next(auto_ids))
197+
198+
return self._map_bucket_aggs(set_id)
199+
200+
def to_json_data(self):
201+
return {
202+
'alias': self.alias,
203+
'bucketAggs': self.bucketAggs,
204+
'metrics': self.metricAggs,
205+
'query': self.query,
206+
'refId': self.refId,
207+
}

0 commit comments

Comments
 (0)