General Splunk

An advanced SPL field trip - sparklines!

December 17th, 2025

Fun tricks to get Splunk's sparklines to work against arbitrary pre-aggregated data

I found something in SPL today that was unexpectedly difficult yet also fun and a little charming. It has to do with Splunk sparklines”. 

These are an old piece of Splunk UI magic, written around 2008 by the inimitable Gareth Watts, back when he and I worked together in the Splunk UI group, a year or two before I left Splunk to start Sideview. 

The basic idea is you stick the word sparkline” into your stats command like so:

`custom_index` 
|  stats sparkline dc(source) as files count as events by sourcetype

and Splunk will obligingly render a little sparkline visualization for each row of your data.

Which although somewhat forgotten in 2025, is still a really powerful tool to give a lot of detail to your users right alongside their more normal tabular metadata.

Today though the thing I wanted to do was to use sparklines on the landing page of the Cisco CDR app. Since it’s not uncommon for our larger customers to have millions of calls a day, running this against the raw data is absolutely out of the question. 

First stop — tstats — surely tstats supports sparkline !

Well… it’s sad but tstats does NOT support sparkline. It dies quietly and if you dig the error out of the search.log it says:

Error in 'TsidxStats': A field for an aggregate function is missing or invalid. 
Aggregate functions require fields with valid values to complete their arguments.

which is certainly no fun. 

Next stop — surely sparkline in stats can work with aggregated rows from tstats!

At first it does not seem to work. If you try:

|  tstats count WHERE `custom_index` GROUPBY sourcetype _time
|  stats sparkline by sourcetype

or sparkline(count) for that matter, you will get this:

It is certainly a sparkline, but it looks funny. Remember that the upstream rows from tstats aren’t raw events, they are aggregated rows each representing a time bucket and each row has an integer-valued count” field in it. While you might want or even expect sparkline to be smart enough to notice these count” fields and use them.… it does not. 

Which means the sparklines in the image above are because it is just graphing 1” when there is any row coming out of tstats, and 0” when there is no row at all there. 

That’s not what we want. 

It turns out…

I missed this, but after writing the first draft of this, Martin Mueller pointed out what I had missed, you need sparkline(sum(count)) and then suddenly it DOES pick up the aggregated counts. 

|  tstats count WHERE `custom_index` GROUPBY sourcetype _time
|  stats sparkline(sum(count)) by sourcetype

And everyone lived happily ever after… 

But what if they didn’t…

What if sparkline(sum(count)) didn’t work and what if we had to use dark magic instead?

It turns out there are some fun things down this road. If you’ve ever exported a sparkline search as CSV you will know a very odd fact. That sparklines use a kind of in band signaling”. Which is evil ! so we’re on the right track and headed towards something fun. Probably.

crack open some sparkline search results in Excel and GAZE UPON THE IN-BAND SIGNALING, IF YOU DARE

After a moment perhaps to recover, we can use this. If we apply the axiom there is no magic in SPL — everything is just rows of key value pairs”, that means… 

… all we have to do is construct these hideous mv values ourselves. 

|  tstats count WHERE `custom_index` GROUPBY sourcetype _time
|  stats count list(count) as mv_counts by sourcetype
|  eval sparkline=mvappend("##__SPARKLINE__##", mv_counts)
|  fields - mv_counts

except… it’s not quite right either. Inspecting the output, it’s not rendering any points where the count is 0.

Which actually makes sense, because tstats GROUPBY _​time never outputs timebuckets with zero counts. It’s.… just one of those weird things. timechart always does output the count=0 rows, but tstats not so much. Why? Well I’d have to tell you a long story involving who sat where in Splunk engineering in 2009 vs 2011 and halfway through it you’d stab me with something. 

OK though… the seal is already broken on Dark SPL Magic” so lets just use some more! 

One of the things you learn about SPL after a long while is that there are two extremely arcane commands xyseries and untable, notable mostly because they make your head hurt. 

  • xyseries — if you pipe stats style” output into this, it can give you the corresponding chart style” output rows.
  • untable — basically the converse… if you pipe chart style” output rows into it, it will give you the stats style” equivalent. 

You can try this at home to learn what I mean when I say stats style” etc. 

index=<YOUR INDEX HERE>
| chart count over host by sourcetype
| untable host sourcetype count

that will give you rows that look exactly like the output from:

index=<YOUR INDEX HERE>
| stats count by host, sourcetype

One corollary is that if you apply BOTH of them you get back exactly where you started. In the following example you can sort of cancel out” the last two lines; delete them and nothing changes.

index=<YOUR INDEX HERE>
| chart count over host by sourcetype
| untable host sourcetype count
| xyseries host sourceytpe count

Which is neat but… omg why do we care? We were talking about sparklines. 

Well because of the thing I said above. tstats GROUPBY _​time never outputs any rows with count=0, but timechart does. So we can cheat and wash our tstats output through a timechart and an untable to get the output we want, including all the count=0 rows.

|  tstats count WHERE `custom_index` GROUPBY sourcetype _time
|  timechart last(count) as count by sourcetype
|  fillnull
|  untable _time sourcetype count
|  stats list(count) as mv_counts by sourcetype
|  eval sparkline=mvappend("##__SPARKLINE__##", mv_counts)
|  fields - mv_counts

(Oh and <handwave> we also needed another splash of weird SPL lore, that you can use fillnull command with no arguments and it will just go fill all the nulls for you everywhere, and the default is 0” which is what we wanted. )

Which finally… gives us sparklines rendered from aggregated rows, ie rows that have _​time” but also already have count” fields in them. 

Success! And that concludes our field trip. 

REMEMBER: the crazier SPL here was just a thought experiment around what if sparkline(sum(count)) didn’t work.” 

but it does work. the real answer is to do:

|  tstats count WHERE `custom_index` GROUPBY sourcetype _time
|  stats sparkline(sum(count)) by sourcetype

And dark magic or no, this allows Sideview to ship a little sparkline visualization on the new 8.3 homepage. 

(Note — this screenshot shows a page rendered entirely by the Sideview UI, not the Splunk UI, but due to Gareth’s sparkline library being open-sourced back in 2009 or so, Sideview’s Table module just uses the same old trusty works 2 decades later” open source sparkline library that Splunk does. Thanks again Gareth!)

Download a 60-day free trial & work with your own live data

Start My Free Trial

By submitting this form, I agree to Sideview's Trial Internal Use License Agreement and Privacy Policy.