Rolling Standard Deviation for N samples triggers UnboundLocalError in specific cases

Asked by Andrew Crowell

I've discovered that the `stdev` function in graphite.render.functions will cause an UnboundLocalError to be triggered in some instances.

Basically, it boils down to a local named sumOfSquares not being initialized on the first N items, because the average is None (the sum is None; there is no data in the first N samples). And later in the series, a non-None average appears, and uninitialized sumOfSquares is passed in doStdDev, causing the error.

How can this be triggered? Well, as a simple replication case, if on the Graphite Composer you want to plot the last 24 hours, and you take the standard deviation of some metric that only has data starting 3 hours ago (ie. the data for the first 21 hours is None), then it'll blow up.

-- Andrew Crowell

Also, for some more diagnostics, here is a real, more complicated example, and the traceback demonstrating the problem:

Environment:

Request Method: GET
Request URL: http://graphite2011:8888/render/?width=586&height=308&_salt=1319579730.737&from=-1days&target=movingAverage(diffSeries(load.1min.web.%5Eroll-up%2Cscale(stdev(load.1min.web.%5Eroll-up%2C50)%2C2))%2C100)&target=movingAverage(sum(load.1min.web.%5Eroll-up%2Cscale(stdev(load.1min.web.%5Eroll-up%2C50)%2C2))%2C100)&target=stdev(load.1min.web.%5Eroll-up%2C50)&target=stdev(load.1min.web.%5Eroll-up%2C50)&target=load.1min.web.%5Eroll-up&target=sumSeries(load.1min.web.%5Eroll-up%2Cscale(stdev(load.1min.web.%5Eroll-up%2C50)%2C2))
Django Version: 1.1.1
Python Version: 2.6.5
Installed Applications:
['graphite.metrics',
 'graphite.render',
 'graphite.cli',
 'graphite.browser',
 'graphite.composer',
 'graphite.account',
 'graphite.dashboard',
 'graphite.whitelist',
 'graphite.events',
 'django.contrib.auth',
 'django.contrib.sessions',
 'django.contrib.admin',
 'django.contrib.contenttypes',
 'tagging']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.middleware.gzip.GZipMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware')

Traceback:
File "/usr/lib/pymodules/python2.6/django/core/handlers/base.py" in get_response
  92. response = callback(request, *callback_args, **callback_kwargs)
File "/opt/graphite/webapp/graphite/render/views.py" in renderView
  105. seriesList = evaluateTarget(requestContext, target)
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTarget
  10. result = evaluateTokens(requestContext, tokens)
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  21. return evaluateTokens(requestContext, tokens.expression)
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  28. args = [evaluateTokens(requestContext, arg) for arg in tokens.call.args]
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  21. return evaluateTokens(requestContext, tokens.expression)
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  28. args = [evaluateTokens(requestContext, arg) for arg in tokens.call.args]
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  21. return evaluateTokens(requestContext, tokens.expression)
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  28. args = [evaluateTokens(requestContext, arg) for arg in tokens.call.args]
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  21. return evaluateTokens(requestContext, tokens.expression)
File "/opt/graphite/webapp/graphite/render/evaluator.py" in evaluateTokens
  29. return func(requestContext, *args)
File "/opt/graphite/webapp/graphite/render/functions.py" in stdev
  1071. (sd, sumOfSquares) = doStdDev(sumOfSquares, toDrop, series[index+time], time, avg)

Exception Type: UnboundLocalError at /render/
Exception Value: local variable 'sumOfSquares' referenced before assignment

Question information

Language:
English Edit question
Status:
Answered
For:
Graphite Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
Nicholas Leskiw (nleskiw) said :
#1

Heh, then don't do that. ; )

I think throwing an error is the appropriate course of action here don't you? You can't really get the standard deviation of a set that contains null values, can you? What would you do for a metric that has values, is null for a bit, has a few more, is null for a bit, has more values?

Seriously, some one with a math degree comment on this, I almost flunked Calc 3 and Linear Algebra...

Revision history for this message
chrismd (chrismd) said :
#2

I just fixed this in trunk, thanks for the bug report. The stdev function is supposed to just skip null values.

Can you help with this problem?

Provide an answer of your own, or ask Andrew Crowell for more information if necessary.

To post a message you must log in.