Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Grafana python datasource - using pandas for timeseries and table data. inspired by and compatible with the simple json datasource
from flask import Flask, request, jsonify, json, abort
from flask_cors import CORS, cross_origin
import pandas as pd
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
methods = ('GET', 'POST')
metric_finders= {}
metric_readers = {}
annotation_readers = {}
panel_readers = {}
def add_reader(name, reader):
metric_readers[name] = reader
def add_finder(name, finder):
metric_finders[name] = finder
def add_annotation_reader(name, reader):
annotation_readers[name] = reader
def add_panel_reader(name, reader):
panel_readers[name] = reader
@app.route('/', methods=methods)
@cross_origin()
def hello_world():
print request.headers, request.get_json()
return 'Jether\'s python Grafana datasource, used for rendering HTML panels and timeseries data.'
@app.route('/search', methods=methods)
@cross_origin()
def find_metrics():
print request.headers, request.get_json()
req = request.get_json()
target = req.get('target', '*')
if ':' in target:
finder, target = target.split(':', 1)
else:
finder = target
if not target or finder not in metric_finders:
metrics = []
if target == '*':
metrics += metric_finders.keys() + metric_readers.keys()
else:
metrics.append(target)
return jsonify(metrics)
else:
return jsonify(list(metric_finders[finder](target)))
def dataframe_to_response(target, df, freq=None):
response = []
if df.empty:
return response
if freq is not None:
orig_tz = df.index.tz
df = df.tz_convert('UTC').resample(rule=freq, label='right', closed='right', how='mean').tz_convert(orig_tz)
if isinstance(df, pd.Series):
response.append(_series_to_response(df, target))
elif isinstance(df, pd.DataFrame):
for col in df:
response.append(_series_to_response(df[col], target))
else:
abort(404, Exception('Received object is not a dataframe or series.'))
return response
def dataframe_to_json_table(target, df):
response = []
if df.empty:
return response
if isinstance(df, pd.DataFrame):
response.append({'type': 'table',
'columns': df.columns.map(lambda col: {"text": col}).tolist(),
'rows': df.where(pd.notnull(df), None).values.tolist()})
else:
abort(404, Exception('Received object is not a dataframe.'))
return response
def annotations_to_response(target, df):
response = []
# Single series with DatetimeIndex and values as text
if isinstance(df, pd.Series):
for timestamp, value in df.iteritems():
response.append({
"annotation": target, # The original annotation sent from Grafana.
"time": timestamp.value // 10 ** 6, # Time since UNIX Epoch in milliseconds. (required)
"title": value, # The title for the annotation tooltip. (required)
#"tags": tags, # Tags for the annotation. (optional)
#"text": text # Text for the annotation. (optional)
})
# Dataframe with annotation text/tags for each entry
elif isinstance(df, pd.DataFrame):
for timestamp, row in df.iterrows():
annotation = {
"annotation": target, # The original annotation sent from Grafana.
"time": timestamp.value // 10 ** 6, # Time since UNIX Epoch in milliseconds. (required)
"title": row.get('title', ''), # The title for the annotation tooltip. (required)
}
if 'text' in row:
annotation['text'] = str(row.get('text'))
if 'tags' in row:
annotation['tags'] = str(row.get('tags'))
response.append(annotation)
else:
abort(404, Exception('Received object is not a dataframe or series.'))
return response
def _series_to_annotations(df, target):
if df.empty:
return {'target': '%s' % (target),
'datapoints': []}
sorted_df = df.dropna().sort_index()
timestamps = (sorted_df.index.astype(pd.np.int64) // 10 ** 6).values.tolist()
values = sorted_df.values.tolist()
return {'target': '%s' % (df.name),
'datapoints': zip(values, timestamps)}
def _series_to_response(df, target):
if df.empty:
return {'target': '%s' % (target),
'datapoints': []}
sorted_df = df.dropna().sort_index()
try:
timestamps = (sorted_df.index.astype(pd.np.int64) // 10 ** 6).values.tolist() # New pandas version
except:
timestamps = (sorted_df.index.astype(pd.np.int64) // 10 ** 6).tolist()
values = sorted_df.values.tolist()
return {'target': '%s' % (df.name),
'datapoints': zip(values, timestamps)}
@app.route('/query', methods=methods)
@cross_origin(max_age=600)
def query_metrics():
print request.headers, request.get_json()
req = request.get_json()
results = []
ts_range = {'$gt': pd.Timestamp(req['range']['from']).to_pydatetime(),
'$lte': pd.Timestamp(req['range']['to']).to_pydatetime()}
if 'intervalMs' in req:
freq = str(req.get('intervalMs')) + 'ms'
else:
freq = None
for target in req['targets']:
if ':' not in target.get('target', ''):
abort(404, Exception('Target must be of type: <finder>:<metric_query>, got instead: ' + target['target']))
req_type = target.get('type', 'timeserie')
finder, target = target['target'].split(':', 1)
query_results = metric_readers[finder](target, ts_range)
if req_type == 'table':
results.extend(dataframe_to_json_table(target, query_results))
else:
results.extend(dataframe_to_response(target, query_results, freq=freq))
return jsonify(results)
@app.route('/annotations', methods=methods)
@cross_origin(max_age=600)
def query_annotations():
print request.headers, request.get_json()
req = request.get_json()
results = []
ts_range = {'$gt': pd.Timestamp(req['range']['from']).to_pydatetime(),
'$lte': pd.Timestamp(req['range']['to']).to_pydatetime()}
query = req['annotation']['query']
if ':' not in query:
abort(404, Exception('Target must be of type: <finder>:<metric_query>, got instead: ' + query))
finder, target = query.split(':', 1)
results.extend(annotations_to_response(query, annotation_readers[finder](target, ts_range)))
return jsonify(results)
@app.route('/panels', methods=methods)
@cross_origin()
def get_panel():
print request.headers, request.get_json()
req = request.args
ts_range = {'$gt': pd.Timestamp(int(req['from']), unit='ms').to_pydatetime(),
'$lte': pd.Timestamp(int(req['to']), unit='ms').to_pydatetime()}
query = req['query']
if ':' not in query:
abort(404, Exception('Target must be of type: <finder>:<metric_query>, got instead: ' + query))
finder, target = query.split(':', 1)
return panel_readers[finder](target, ts_range)
if __name__ == '__main__':
# Sample annotation reader : add_annotation_reader('midnights', lambda query_string, ts_range: pd.Series(index=pd.date_range(ts_range['$gt'], ts_range['$lte'], freq='D', normalize=True)).fillna('Text for annotation - midnight'))
# Sample timeseries reader :
# def get_sine(freq, ts_range):
# freq = int(freq)
# ts = pd.date_range(ts_range['$gt'], ts_range['$lte'], freq='H')
# return pd.Series(np.sin(np.arange(len(ts)) * np.pi * freq * 2 / float(len(ts))), index=ts).to_frame('value')
# add_reader('sine_wave', get_sine)
# To query the wanted reader, use `<reader_name>:<query_string>`, e.g. 'sine_wave:24'
app.run(host='0.0.0.0', port=3003, debug=True)
@RonanHiggins

This comment has been minimized.

Copy link

commented Aug 1, 2017

Hi Linar, thanks for the sharing this great piece of code..
It's helped me along a lot, while the time series works great and as expected. The table response option doesn't work for me. The grafana side raises an error and asks for a datapoints entry in the file. Furthermore the js server version has a values entry instead of rows in response table json (https://github.com/bergquist/fake-simple-json-datasource/blob/master/index.js#L43) any ideas on how to proceed.. so close to greatness here! The js server version also gives the datapoints error. I'm running grafana 4.4.1.

@linar-jether

This comment has been minimized.

Copy link
Owner Author

commented Aug 22, 2017

HI @RonanHiggins,
Note that the js datasource has an open pull request to fix the table response for a long time (https://github.com/bergquist/fake-simple-json-datasource/pull/3/files), this version works fine...

Make sure you select table instead of timeserie in the table panel's metrics setting
screenshot 106

@kb1lqc

This comment has been minimized.

Copy link

commented Sep 21, 2017

@linar-jether thanks as well for this code. Could you explain a bit about the /search response and how what the javascript in the simple-json-datasource sends vs what you reply with. I'm having difficulty wrapping my head around the expected operation such that I can modify this for my own needs.

My current understanding is that the `/search' URL should return a map of all time series items available. Is this true? For example if my time series data has items such as "temperature" or "PacketsTX" I would return a JSON item with these in it?

@linar-jether

This comment has been minimized.

Copy link
Owner Author

commented Sep 26, 2017

@kb1lqc, The search response should return a list of strings, e.g. if I've added a finder

nodes_mapping = {'type_a': ['node1', 'node2', 'node3'],
                 'type_b': ['other_node1', 'other_node2', 'other_node3']}

add_finder('get_nodes', lambda q: nodes_mapping.get(q, nodes_mapping.keys()) if q != '*' else sum(nodes_mapping.values(), []))

Then through the simple-json-datasource query editor i can enter get_nodes:type_a or get_nodes:* to get the nodes completion.
screenshot 122

Although this works for completion, the main use case is for template variables...

screenshot 123
screenshot 124

@kchandan

This comment has been minimized.

Copy link

commented Nov 21, 2017

I simply downloaded the code, but the /search method does not work. Getting error, as per the simplejson plugin it should.

[21/Nov/2017 14:47:03] "GET /search HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/lib64/python2.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/lib64/python2.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/lib64/python2.7/site-packages/flask_cors/decorator.py", line 128, in wrapped_function
    resp = make_response(f(*args, **kwargs))
  File "/home/vagrant/python-datasource.py", line 47, in find_metrics
target = req.get('target', '*')
AttributeError: 'NoneType' object has no attribute 'get'

@linar-jether

This comment has been minimized.

Copy link
Owner Author

commented Nov 26, 2017

@kchandan Just tested with simple-json-plugin version > 1.3.1, and works as expected, which version are you using?

@JosPolfliet

This comment has been minimized.

Copy link

commented Dec 1, 2017

To run in Python 3.x, change the print statements and in _series_to_response change the last line to

    return {'target': '%s' % (df.name),
            'datapoints': list(zip(values, timestamps))}

You need to add the list() call because Py3 returns an iterator.

Great gist, OP!

@adrianlzt

This comment has been minimized.

Copy link

commented Dec 19, 2017

@kchandan test with a newer flask version. I was getting the same error with

Flask==0.10.1
@linar-jether

This comment has been minimized.

Copy link
Owner Author

commented Jan 10, 2018

To serve HTML panels, use add_panel_reader(name, lambda query_str, ts_range: <html_content>) to register a function that returns an HTML string for a given query string and time range. then to query use the ajax panel.

image

@Sequential-circuits

This comment has been minimized.

Copy link

commented Jan 18, 2018

Sorry for the probably stupid question, but I gather that this app needs to be fed with data somehow. How do I do it? I have created succesfully the json datasource at graphana, but it shows no graphs, so I gather this app needs to be fed with sometihing. Thanks! :)

@linar-jether

This comment has been minimized.

Copy link
Owner Author

commented Jan 18, 2018

@Sequential-circuits, basically, the data the readers provide is just a pandas DataFrame object, how you build that data frame is up to you, read from CSVs, other databases, network endpoints or whatever you want.
The reader provider a pandas dataframe in response to the user's query,

@Sequential-circuits

This comment has been minimized.

Copy link

commented Jan 19, 2018

Thank you for your quick answer :)
So, basically, I have to install Pandas and read its manual to understand how to feed it with data, and your object will gather the data from Pandas, correct?

@saptharsh

This comment has been minimized.

Copy link

commented Jan 24, 2018

Hi Linar Jether,
I am trying to use your code on Python 2 environment, I have tried on Python 3 as well. I am interested in the /query and /search functionality.
I see an error with '/query' with json request similar to:
{
"panelId": 1,
"range": {
"from": "2016-10-31T06:33:44.866Z",
"to": "2016-10-31T12:33:44.866Z",
"raw": {
"from": "now-6h",
"to": "now"
}
},
"rangeRaw": {
"from": "now-6h",
"to": "now"
},
"interval": "30s",
"intervalMs": 30000,
"targets": [
{ "target": "upper_50", "refId": "A", "type": "timeserie" },
{ "target": "upper_75", "refId": "B", "type": "timeserie" }
],
"format": "json",
"maxDataPoints": 550
}
The error response:
Not Found
Target must be of type: :<metric_query>, got instead: upper_50

I might be missing some trivial things here. Can you please help me with this ?
--Thanks so much

@linar-jether

This comment has been minimized.

Copy link
Owner Author

commented Jan 25, 2018

@saptharsh You need to supply a query string in the form of <target_reader>:<query_string>
where target_reader is the object registered using add_reader('reader_name', get_data), and the query_string is that argument passed to the reader along with the time range

@gitrc

This comment has been minimized.

Copy link

commented Jun 8, 2018

thank you for providing this gist, it is quite useful!

@KoushikMuthakana

This comment has been minimized.

Copy link

commented Jun 28, 2018

@linar-jether I download this code and connected to grafana server, but i am not able to get anything in metric options. could you please explain how to create metrices.

@dwinaandrea

This comment has been minimized.

Copy link

commented Jun 29, 2018

hi, i tried this code, and i got error on all endpoint except /
this is what i got when i open /search
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36
Connection: keep-alive
Host: 127.0.0.1:5000
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br

None
[2018-06-29 15:27:36,245] ERROR in app: Exception on /search [GET]
Traceback (most recent call last):
File "/home/nakama/.local/lib/python2.7/site-packages/Flask-1.0.2-py2.7.egg/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/home/nakama/.local/lib/python2.7/site-packages/Flask-1.0.2-py2.7.egg/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/nakama/.local/lib/python2.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/home/nakama/.local/lib/python2.7/site-packages/Flask-1.0.2-py2.7.egg/flask/app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/nakama/.local/lib/python2.7/site-packages/Flask-1.0.2-py2.7.egg/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/home/nakama/.local/lib/python2.7/site-packages/Flask-1.0.2-py2.7.egg/flask/app.py", line 1799, in dispatch_request
return self.view_functionsrule.endpoint
File "/home/nakama/.local/lib/python2.7/site-packages/flask_cors/decorator.py", line 128, in wrapped_function
resp = make_response(f(args, **kwargs))
File "/home/nakama/Documents/Tokopedia/Dwina Andrea-old/Quality Assurance - Alpha5/14. Prometheus/flask/flask/hello.py", line 48, in find_metrics
target = req.get('target', '
')
AttributeError: 'NoneType' object has no attribute 'get'
i hope you can help me with this. Thank you.

@srik2110

This comment has been minimized.

Copy link

commented Aug 6, 2018

Thanks for sharing your code. I have some trouble using your code. I have been trying to get metrics into grafana but I am missing somewhere.

I have changed the following :

data = {'targets':[{'target':1, 'datapoints':[[1, 1444000], [12, 1349000]]},
             {'target':2, 'datapoints':[[121, 14444000], [212, 21349000]]}
]}
metric_readers = data

Is this the way correct, or am I missing somewhere. Kindly help me out.

Also, I have understood metric_readers are the ones used to read the data and metric_finders are to filter data. Is my understanding correct? Wondering what to give in metric_finders? It would be nice if you provide an example.

@linar-jether

This comment has been minimized.

Copy link
Owner Author

commented Aug 6, 2018

@srik2110 See the sample code in the __main__ section comments, metric_readers contains functions that return pandas Dataframes for a given time range

@srik2110

This comment has been minimized.

Copy link

commented Aug 6, 2018

Thank you very much for replying.
I am wondering how to provide data source which I have it in a dataframe. Is it through the functions in metric_readers that I provide the data source?
In that case, where would /search take keys from? (line 57 : metrics += metric_finders.keys() + metric_readers.keys() or line 63: metric_findersfinder).

Actually I am kind of stuck, as I am not getting any metrics in grafana, which I thought would mean /search is not taking keys. What do you suggest me to resolve this?

@dridk

This comment has been minimized.

Copy link

commented Oct 23, 2018

Same error than @kchandan . I fixed Python 3 compatibility, but the following code doesn't make search request work.

With Python 3 / Flask :

from flask import Flask, request, jsonify, json, abort
from flask_cors import CORS, cross_origin

import pandas as pd

app = Flask(__name__)

cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'

methods = ('GET', 'POST')

metric_finders= {}
metric_readers = {}
annotation_readers = {}
panel_readers = {}


def add_reader(name, reader):
    metric_readers[name] = reader


def add_finder(name, finder):
    metric_finders[name] = finder


def add_annotation_reader(name, reader):
    annotation_readers[name] = reader


def add_panel_reader(name, reader):
    panel_readers[name] = reader


@app.route('/', methods=methods)
@cross_origin()
def hello_world():
    print(request.headers, request.get_json())
    return 'Jether\'s python Grafana datasource, used for rendering HTML panels and timeseries data.'

@app.route('/search', methods=methods)
@cross_origin()
def find_metrics():
    print (request.headers, request.get_json())
    req = request.get_json()

    print("type " ,type(request))


    target = req.get('target', '*')

    if ':' in target:
        finder, target = target.split(':', 1)
    else:
        finder = target

    if not target or finder not in metric_finders:
        metrics = []
        if target == '*':
            metrics += metric_finders.keys() + metric_readers.keys()
        else:
            metrics.append(target)

        return jsonify(metrics)
    else:
        return jsonify(list(metric_finders[finder](target)))


def dataframe_to_response(target, df, freq=None):
    response = []

    if df.empty:
        return response

    if freq is not None:
        orig_tz = df.index.tz
        df = df.tz_convert('UTC').resample(rule=freq, label='right', closed='right', how='mean').tz_convert(orig_tz)

    if isinstance(df, pd.Series):
        response.append(_series_to_response(df, target))
    elif isinstance(df, pd.DataFrame):
        for col in df:
            response.append(_series_to_response(df[col], target))
    else:
        abort(404, Exception('Received object is not a dataframe or series.'))

    return response


def dataframe_to_json_table(target, df):
    response = []

    if df.empty:
        return response

    if isinstance(df, pd.DataFrame):
        response.append({'type': 'table',
                         'columns': df.columns.map(lambda col: {"text": col}).tolist(),
                         'rows': df.where(pd.notnull(df), None).values.tolist()})
    else:
        abort(404, Exception('Received object is not a dataframe.'))

    return response


def annotations_to_response(target, df):
    response = []

    # Single series with DatetimeIndex and values as text
    if isinstance(df, pd.Series):
        for timestamp, value in df.iteritems():
            response.append({
                "annotation": target, # The original annotation sent from Grafana.
                "time": timestamp.value // 10 ** 6, # Time since UNIX Epoch in milliseconds. (required)
                "title": value, # The title for the annotation tooltip. (required)
                #"tags": tags, # Tags for the annotation. (optional)
                #"text": text # Text for the annotation. (optional)
            })
    # Dataframe with annotation text/tags for each entry
    elif isinstance(df, pd.DataFrame):
        for timestamp, row in df.iterrows():
            annotation = {
                "annotation": target,  # The original annotation sent from Grafana.
                "time": timestamp.value // 10 ** 6,  # Time since UNIX Epoch in milliseconds. (required)
                "title": row.get('title', ''),  # The title for the annotation tooltip. (required)
            }

            if 'text' in row:
                annotation['text'] = str(row.get('text'))
            if 'tags' in row:
                annotation['tags'] = str(row.get('tags'))

            response.append(annotation)
    else:
        abort(404, Exception('Received object is not a dataframe or series.'))

    return response

def _series_to_annotations(df, target):
    if df.empty:
        return {'target': '%s' % (target),
                'datapoints': []}

    sorted_df = df.dropna().sort_index()
    timestamps = (sorted_df.index.astype(pd.np.int64) // 10 ** 6).values.tolist()
    values = sorted_df.values.tolist()

    return {'target': '%s' % (df.name),
            'datapoints': zip(values, timestamps)}


def _series_to_response(df, target):
    if df.empty:
        return {'target': '%s' % (target),
                'datapoints': []}

    sorted_df = df.dropna().sort_index()

    try:
        timestamps = (sorted_df.index.astype(pd.np.int64) // 10 ** 6).values.tolist() # New pandas version
    except:
        timestamps = (sorted_df.index.astype(pd.np.int64) // 10 ** 6).tolist()

    values = sorted_df.values.tolist()

    return {'target': '%s' % (df.name),
            'datapoints': list(zip(values, timestamps))}


@app.route('/query', methods=methods)
@cross_origin(max_age=600)
def query_metrics():
    print (request.headers, request.get_json())
    req = request.get_json()

    results = []

    ts_range = {'$gt': pd.Timestamp(req['range']['from']).to_pydatetime(),
                '$lte': pd.Timestamp(req['range']['to']).to_pydatetime()}

    if 'intervalMs' in req:
        freq = str(req.get('intervalMs')) + 'ms'
    else:
        freq = None

    for target in req['targets']:
        if ':' not in target.get('target', ''):
            abort(404, Exception('Target must be of type: <finder>:<metric_query>, got instead: ' + target['target']))

        req_type = target.get('type', 'timeserie')

        finder, target = target['target'].split(':', 1)
        query_results = metric_readers[finder](target, ts_range)

        if req_type == 'table':
            results.extend(dataframe_to_json_table(target, query_results))
        else:
            results.extend(dataframe_to_response(target, query_results, freq=freq))

    return jsonify(results)


@app.route('/annotations', methods=methods)
@cross_origin(max_age=600)
def query_annotations():
    print (request.headers, request.get_json())
    req = request.get_json()

    results = []

    ts_range = {'$gt': pd.Timestamp(req['range']['from']).to_pydatetime(),
                '$lte': pd.Timestamp(req['range']['to']).to_pydatetime()}

    query = req['annotation']['query']

    if ':' not in query:
        abort(404, Exception('Target must be of type: <finder>:<metric_query>, got instead: ' + query))

    finder, target = query.split(':', 1)
    results.extend(annotations_to_response(query, annotation_readers[finder](target, ts_range)))

    return jsonify(results)


@app.route('/panels', methods=methods)
@cross_origin()
def get_panel():
    print (request.headers, request.get_json())
    req = request.args

    ts_range = {'$gt': pd.Timestamp(int(req['from']), unit='ms').to_pydatetime(),
                '$lte': pd.Timestamp(int(req['to']), unit='ms').to_pydatetime()}

    query = req['query']

    if ':' not in query:
        abort(404, Exception('Target must be of type: <finder>:<metric_query>, got instead: ' + query))

    finder, target = query.split(':', 1)
    return panel_readers[finder](target, ts_range)


if __name__ == '__main__':
    # Sample annotation reader : 

    add_annotation_reader('midnights', lambda query_string, ts_range: pd.Series(index=pd.date_range(ts_range['$gt'], ts_range['$lte'], freq='D', normalize=True)).fillna('Text for annotation - midnight'))
    # Sample timeseries reader : 
    def get_sine(freq, ts_range):
           freq = int(freq)
           ts = pd.date_range(ts_range['$gt'], ts_range['$lte'], freq='H')
           return pd.Series(np.sin(np.arange(len(ts)) * np.pi * freq * 2 / float(len(ts))), index=ts).to_frame('value')
    
    add_reader('sine_wave', get_sine)

    # To query the wanted reader, use `<reader_name>:<query_string>`, e.g. 'sine_wave:24' 

    app.run(host='0.0.0.0', port=3003, debug=True)

Search request returns me an error .

127.0.0.1 - - [23/Oct/2018 17:15:29] "POST /search HTTP/1.1" 200 -
Host: localhost:3003
User-Agent: Grafana/5.3.1
Content-Length: 13
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Content-Type: application/json
Origin: http://localhost:3000
Referer: http://localhost:3000/dashboard/new?gettingstarted&panelId=2&fullscreen&edit&orgId=1
X-Forwarded-For: ::1, ::1
X-Grafana-Nocache: true
X-Grafana-Org-Id: 1

 {'target': ''}
@rakeshrocckz111

This comment has been minimized.

Copy link

commented Dec 4, 2018

My code below is getting a json from some database and my output will be in JSON format. I want to populate the JSON output in table format in grafana by using simple JSON plugin in python(Flask). please help me with /search, /query, tragets, range from, range too. I did not understand how you defined your code.my query name is "Food" and it should have two columns "vegetables" and "fruits" in the grafana. please help me how to define it.

from flask import  FLASK
 import cx_Oracle, json
dbconnection = cx_Oracle.makedsn('','',servicename='')

app = Flask(__name__)

@app.route('/')
def healthtest():
return "Good" 

@app.route('/search', methods=['POST'])
req = req.get_json():
def searching():

 return searching
@app.route('/query',methods=['GET'])



if  target == "Food":
conn = cx_Oracle.connect(user='',password='',dsn=dsn_tns)
c.execute('''SELECT APPLE, BANANA, CARROT FROM VEGETABLES''')
for row in c:
print (row)
conn.close()
else :
 print "No DATA"

  app.run(host=0.0.0.0, port=0000, debug=True

My JSON output will be like this
Output: {"vegetables":"carrot","fruit":"apple","banana":"fruit"}

I want to show this output in table format in grafana in python

(Flask)

My query name is "Food" and I have two columns "Fruits" and "vegetables".please help me how to define in "/search" and "/query".Please help me out i am not at all understanding the grafana documentation.

@macd2

This comment has been minimized.

Copy link

commented Dec 5, 2018

Hey there,
great code!
unfortunately im not sure on how to extend it i got the example to run.

What i would like to achieve is, reading an entire Dataframe in a loop:
something like:

while True:
    df = pd.DataFrame(lst)
    --> and here i would like to add your code to read the df and send it to Grafana
```

any suggestions on that? 
 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.