Customizing graphs in Zenoss

Zenoss has a pretty nice integration with rrdtool. You can get a lot of pretty graphs out of it. I like to take these graphs and put them on the company intranet for other employees to enjoy†. I also find that it’s quite helpful to show some of the more important graphs in one place (like response time for the production web servers or temperature in the server closets).

The URL to the Zenoss graph image has a few parameters and a big old blob of base64 data. This data is the actual rrd commands to build the graph. Unfortunately, it’s not really easy to get at this data. The data is a base64 encoded, zipped, pipe-delimited bunch of RRD commands. To alter the graph, all you have to do is decode this data, change the RRD parameters, encoded it, and send it back to Zenoss. Zenoss will happily display the graph with the new configuration … and poof, instant realtime rrd image server.

When I first wanted to customize a graph, I decoded the blob on the python command line. I got annoyed with this process almost immediately and figured a tool would help, so I wrote one. See: Zenoss RRD Graph Helper.

This is what you do:
Grab the link to your favorite RRD image on your Zenoss server.
Head over my Zenoss RRD Graph Helper.
Paste in your URL, click decode.
Alter the RRD statements, click Encode.
Copy the new URL and put it into your favorite website for others to enjoy.

Per request, here’s the meat of the decoding process:

from zlib import compress, decompress
from base64 import b64encode, b64decode

def decode_gopts(gopts):
    sbz = gopts.replace('-','+').replace('_','/')
    sb = b64decode(sbz)
    s = decompress(sb)
    s = '\n'.join(s.split('|'))

    return s

def encode_ungopts(ungopts):
    s = '|'.join(ungopts.split('\r\n'))
    sz = compress(s, 9)
    szb = b64encode(sz)
    szb_safe = szb.replace('/','_').replace('+','-')

    # strip newlines (created by b64 encode)
    gopts = szb_safe.replace('\n','')

    return gopts

If you want to mix in a few graphs together, you can decode the images that you want to merge, grab the commands you need, then create a totally new graph. I’m using Zenoss Core 2.3. Different versions may yield different results.

And yes, I know that you can achieve similary results by using custom graph commands and/or using the multigraph reporting features. But that can be a more complicated and less obvious process. Sometimes you need the basics.

Footnote: Allow anonymous access to the RenderServer
This information is readily available on the ‘net, and not too hard to figure out. But I’ll include it here for completeness.
Zenoss’s RenderServer (like everything else in Zenoss) requires authorization to see. This means if you just copy a graph url and paste it somewhere else, you won’t see the image unless you have already logged in to Zenoss. Fortunately you can overcome this pretty quickly. Go to: http://your-zenoss:8080/zport/RenderServer/manage_access, and set RenderServer to anonymous. This will allow anonymous access to the RenderServer, and you’ll be able to see the images anywhere.

As a certified mathematician, I get a lot more out of graphs than most people.



  1. #1 by Owen on April 7, 2010 - 1:38 pm

    Any chance you could share the code or explain a bit more about the base64/ziping process?

    • #2 by sethrh on April 8, 2010 - 12:36 pm

      Sure thing. I’ve updated the post to include the meat of the decoding. It (decode_gopts) peels of each layer of encoding one at a time to get to the original rrd statements. The reciprocal function (encode_ungopts) puts them all back.

  2. #3 by Gerald Fontejon on April 20, 2011 - 12:28 pm

    This looks promising. Going to give this one a try. Thanks for sharing.

  3. #4 by Gerald Fontejon on May 5, 2011 - 11:21 am

    Hey there seth. First, Great post. Second, I was wondering if I could send you snippet of python test code to see how your code works. I find merging graphs together to be facinating – however you are right – I have to learn the basics first. Please send me your email address, and I appreciate your time.

  4. #5 by killhup on May 12, 2011 - 3:36 pm

    Hi seth. I’ve been messing with your code for a few hours, however could you tell me what I might be doing wrong. I’m not receiving the exact same gopts value when I re-encode. Thanks in advance.

    == FILE ==
    #!/usr/bin/env python

    from zlib import compress, decompress
    from base64 import b64encode, b64decode

    def encode_ungopts(ungopts):
    s = ‘|’.join(ungopts.split(‘\r\n’))
    sz = compress(s, 9)
    szb = b64encode(sz)
    szb_safe = szb.replace(‘/’,’_’).replace(‘+’,’-‘)
    # strip newlines (created by b64 encode)
    gopts = szb_safe.replace(‘\n’,”)
    return gopts

    f = open(‘ungopts.txt’, ‘r’)
    lines = f.readlines()

    string = ”.join(lines)
    output = encode_ungopts(string)
    print output

    == FILE ==

    • #6 by sethrh on May 12, 2011 - 4:53 pm

      Aside from possibly eating a linefeed or two, your ‘gopts’ values should survive the encoding and decoding process unchanged. If you mean that the base-64 string is coming out differently, that happens because there’s a different byte or two in the text file (your text editor added a new line or added a few spaces or something).

      • #7 by killhup on May 12, 2011 - 5:16 pm

        Awesome – thanks for your help. Got it working with:

        string = ‘|’.join(lines)
        no_nline_string = string.replace(‘\n’,”)
        output = encode_ungopts(no_nline_string)
        print output

        Thanks sethrh.

  5. #8 by Dale Johnson on April 17, 2012 - 7:50 am

    This is wonderful! Thanks so much!!

    For Perl folks out there, here’s the Perl translation:

    use MIME::Base64;
    use Compress::Zlib;
    use strict;

    sub URL2RRD {
    my ($url) = @_;

    my ($gopts) = ($url =~ m/gopts=([^&]+)&/g);
    $gopts =~ tr|-_|+/|;
    my $decoded = decode_base64($gopts);
    my $uncompressed = uncompress($decoded);
    my $lines = join(“\n”, split(/\|/, $uncompressed)).”\n”;

    sub RRD2URL {
    my ($rrd,%args) = @_;
    my $width = $args{width} || 500;
    my $drange = $args{drange} || 129600;
    my $server = $args{server} || ‘’;

    my $piped = join(‘|’, split(/\n/, $rrd));
    my $compressed = compress($piped);
    my $encoded = encode_base64($compressed);
    $encoded =~ tr|+/|-_|;
    $encoded =~ s/\n//g;
    my $url = “http://$server:8080/zport/RenderServer/render?” .

  6. #9 by Rob on August 29, 2012 - 6:37 pm

    Thanks for the info! This post inspired me to create an external front-end to zenoss to view interface graphs. The main reason behind this is zenoss doesn’t allow your to enter in date ranges the way cacti did. This currently only works for interface throughput graphs.. which I might extend to others later on.

  1. links for 2009-07-22 « The Shining Path of Least Resistance

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: