Skip to content

Commit

Permalink
Fix JSON performance regression caused by graphite-project#1710
Browse files Browse the repository at this point in the history
- Removed slow FloatEncoder, using json.dumps again instead
- Still getting good behavior with regard to inf, -inf, and NaN
by converting the values in the data to max & min float value and
None respectively. allow_nan=False prevents bad values from
accidentally being rendered in the JSON.
  • Loading branch information
rehevkor5 committed Jan 3, 2017
1 parent 73bc058 commit 40e6263
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 74 deletions.
59 changes: 0 additions & 59 deletions webapp/graphite/render/float_encoder.py

This file was deleted.

39 changes: 28 additions & 11 deletions webapp/graphite/render/views.py
Expand Up @@ -28,6 +28,8 @@
except ImportError:
import pickle

from sys import float_info

from graphite.compat import HttpResponse
from graphite.util import getProfileByUsername, json, unpickle
from graphite.remote_storage import connector_class_selector
Expand All @@ -37,7 +39,6 @@
from graphite.render.functions import PieFunctions
from graphite.render.hashing import hashRequest, hashData
from graphite.render.glyph import GraphTypes
from graphite.render.float_encoder import FloatEncoder

from django.http import HttpResponseServerError, HttpResponseRedirect
from django.template import Context, loader
Expand Down Expand Up @@ -133,7 +134,19 @@ def renderView(request):
return response

if format == 'json':
jsonStart = time()
series_data = []

def fix_special_values(val_to_fix):
if val_to_fix == float('inf'):
return float_info.max
elif val_to_fix == float('-inf'):
return float_info.min
elif val_to_fix != val_to_fix:
return None
else:
return val_to_fix

if 'maxDataPoints' in requestOptions and any(data):
startTime = min([series.start for series in data])
endTime = max([series.end for series in data])
Expand All @@ -155,37 +168,41 @@ def renderView(request):
timestamps = range(int(series.start), int(series.end) + 1, int(secondsPerPoint))
else:
timestamps = range(int(series.start), int(series.end) + 1, int(series.step))
datapoints = zip(series, timestamps)
datapoints = zip((fix_special_values(v) for v in series), timestamps)
series_data.append(dict(target=series.name, datapoints=datapoints))
elif 'noNullPoints' in requestOptions and any(data):
def index_to_timestamp(index, series):
return series.start + (index * series.step)

for series in data:
values = []
for (index,v) in enumerate(series):
if v is not None:
timestamp = series.start + (index * series.step)
values.append((v,timestamp))
values = filter(lambda (v, timestamp): v is not None,
map(lambda (index, v): (fix_special_values(v), index_to_timestamp(index, series)),
enumerate(series)))
if len(values) > 0:
series_data.append(dict(target=series.name, datapoints=values))
else:
for series in data:
timestamps = range(int(series.start), int(series.end) + 1, int(series.step))
datapoints = zip(series, timestamps)
datapoints = zip((fix_special_values(v) for v in series), timestamps)
series_data.append(dict(target=series.name, datapoints=datapoints))

output = json.dumps(series_data, allow_nan=False)

if 'jsonp' in requestOptions:
response = HttpResponse(
content="%s(%s)" % (requestOptions['jsonp'], json.dumps(series_data, cls=FloatEncoder)),
content="%s(%s)" % (requestOptions['jsonp'], output),
content_type='text/javascript')
else:
response = HttpResponse(content=json.dumps(series_data, cls=FloatEncoder),
response = HttpResponse(content=output,
content_type='application/json')

if useCache:
cache.add(requestKey, response, cacheTimeout)
patch_response_headers(response, cache_timeout=cacheTimeout)
else:
add_never_cache_headers(response)
log.rendering('Total json rendering time %.6f' % (time() - start))
log.rendering('JSON rendering time %.6f' % (time() - jsonStart))
log.rendering('Total time %.6f' % (time() - start))
return response

if format == 'dygraph':
Expand Down
8 changes: 4 additions & 4 deletions webapp/tests/test_render.py
Expand Up @@ -109,17 +109,17 @@ def test_render_view(self):
self.assertEqual(response.content, raw_response)

response = self.client.get(url, {'target': 'test', 'format': 'json'})
self.assertIn('[1e9999, ' + str(ts - 2) + ']', response.content)
self.assertIn('[-1e9999, ' + str(ts - 1) + ']', response.content)
self.assertIn('[1.7976931348623157e+308, ' + str(ts - 2) + ']', response.content)
self.assertIn('[2.2250738585072014e-308, ' + str(ts - 1) + ']', response.content)
data = json.loads(response.content)
end = data[0]['datapoints'][-7:]
self.assertEqual(
end, [[None, ts - 6],
[0.12345678901234568, ts - 5],
[0.4, ts - 4],
[0.6, ts - 3],
[float('inf'), ts - 2],
[float('-inf'), ts - 1],
[1.7976931348623157e+308, ts - 2],
[2.2250738585072014e-308, ts - 1],
[None, ts]])

response = self.client.get(url, {'target': 'test', 'format': 'dygraph'})
Expand Down

0 comments on commit 40e6263

Please sign in to comment.