-
-
Save linar-jether/95ff412f9d19fdf5e51293eb0c09b850 to your computer and use it in GitHub Desktop.
| 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) |
Hi,
@linar-jether added the note to the Gist's title recently, but nevertheless we would like to add a corresponding comment here: We converged this code into a dedicated repository grafana-pandas-datasource the other day [1] and will be happy to receive contributions of any kind. The program is also just making it into the grafana-awesome compilation [2] curated by @zuchka - thanks!
Thank you again, Linar!
With kind regards,
Andreas.
[1] grafana-toolbox/grafana-pandas-datasource#1
[2] zuchka/grafana-awesome#3
TypeError: DataFrame.resample() got an unexpected keyword argument 'how'
if I use create_app() it doesn't give this error but any print('test',flush=True) doesn't print anything on console .I tried debugger and many forms of print ..but failed . Now i can run this app but fail to understand the flow using print . Is it must of use create_app() function in code .
How can I now print values on console using print Any suggestions ?
Hi @tvirmani,
let us know if you have any problems to get grafana-pandas-datasource working. If you find any flaws, please report them on its issue tracker.
With kind regards,
Andreas.
I'm triying to create a dashboard to visualize some data from pandas dataframe.

As a beginner using grafana, the documentation lacks for detailled explanation for beginners in how to configure SimpleJson datasource and use it. After some search on internet I try to run this script and have the following error :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <title>400 Bad Request</title> <h1>Bad Request</h1> <p>Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)</p>after some search on internet, It seems to be a Flask problem, but I'm not sure.I'm stuck in this step
Can anyone what Im doing wrong? Thanks in advance