Technical Detail: Control Bin Selection and Transparent Index Switching in Splunk

Posted by David Veuve - 2011-05-10 16:49:03
Why?: The problems I wanted to solve were controlling timechart bin selection, and providing transparent index switching in Splunk. The solution relied heavily on Sideview Utils, created by Nick Mealy of Sideview, LLC. Example App: I could probably whip one up pretty easily, but I don't want to spend the time unless there's a demand. Let me know if the comments, or via the contact form above, if there is any such desire. Requirements: Set up Summary Indexing so that your fields will be the same between raw data, hourly summary, daily summary, or whatever levels you want to set up. (If your fields don't match currently, you can also FIELDALIAS your raw fields to the summary names, which is what I do.) You'll need to be using Sideview Utils, with the advanced XML, and you'll need at least some understanding of javascript if you need to debug, or want to make any changes. View Changes: Essentially, what you do is pull out a CustomBehavior which will generate a lot of variables, and push them all downstream.

Note that you have to put the CustomBehavior after a <module name="Search">. The reason for this is that if you're using relative time windows (which you will almost always be doing), putting the CustomBehavior after the <module name="Search"> will resolve those relative time ranges to absolute time ranges that you can use for math. This avoids the conundrum of trying to manually figure out what -4d@w+2h really means, programmatically. (This is one of those things that you would expect Splunk to have implemented, but it isn't. This is definitely a hacky approach to getting around the problem.) Obviously, though, you don't want to run your real search, because that would take forever. Searching for foo NOT foo is very quick, so it will resolve the time windows with only a brief delay (probably less than 1/3 second, your mileage may vary).

The code to put in your view:

<module name="TimeRangePicker">
  <param name="default">Last 30 days</param>
  <param name="searchWhenChanged">True</param>

  <module name="Search" autoRun="True">
    <param name="search">* | head 1</param>

    <module name="CustomBehavior">
      <param name="customBehavior">GatherBins</param>
      <param name="requiresDispatch">True</param>
      <module name="HTML" layoutPanel="panel_row1_col1">
        <param name="html"><![CDATA[<br /><p>Looking at $Bins$ bins of $Binsize$ each (alternatively, a span of $Span$).</p>]]></param>
      </module>
    </module>
  </module>

</module>

Application.js Changes: The next step in the process is the actual code in your application.js. I think this should generally be readable without a full explanation. Essentially, I go through and based on the time span in question, I set up the number of bins. Then I do a similar calculation to decide what index to use. In my environment, I need to massage the raw data with an eval statement, so I added that as an extra field. In my actual dashboards, that shows up as a index=$ChooseIndex$ $ChooseEval$ | timechart MyField.
if(typeof(Sideview)!="undefined"){
  $(document).bind("allModulesInHierarchy",function(){

    Sideview.utils.forEachModuleWithCustomBehavior("pickAppropriateColumns",function(b,a){
      a.isReadyForContextPush = function(){
        if(!this.RetrievedBinCount) return Splunk.Module.DEFER;
        if (this.getLoadState() < Splunk.util.moduleLoadStates.HAS_CONTEXT)
          return false;
          return true
             }
      a.onJobProgress = function() {
        var c=this.getContext();
         var d=c.get("search").job;
        this.Bins = 0;
        this.Binsize = "";
        var latest = new Date(d._latestTime);
        var earliest = new Date(d._earliestTime);
        var earliestEpoch = earliest.valueOf() / 1000;
        var latestEpoch = latest.valueOf() / 1000;
        if(latest.valueOf() == 0){
          latest = new Date();
        }
        var Difference = (latest.valueOf() - earliest.valueOf()) / 1000;
        this.RetrievedBinCount = true;
        this.FractionOfMonth = Difference / (30*24*60*60);
        this.Difference = Difference;
        if(Difference > (730*24*60*60)){
          //More than 730 days -- summarize four days
          this.Bins = parseInt(Difference / (96*60*60))+2;
          this.Binsize = "Four Days";
          this.Span = "4d";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else if(Difference > (450*24*60*60)){
          //More than 450 days -- summarize two days
          this.Bins = parseInt(Difference / (48*60*60))+2;
          this.Binsize = "Two Days";
          this.Span = "2d";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else if(Difference > (150*24*60*60)){
          //More than 150 days -- summarize daily
          this.Bins = parseInt(Difference / (24*60*60))+2;
          this.Binsize = "One Day";
          this.Span = "1d";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else if(Difference > (100*24*60*60)){
          //More than 100 days -- summarize 12 hourly
          this.Bins = parseInt(Difference / (12*60*60))+2;
          this.Binsize = "12 Hours";
          this.Span = "12h";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else if(Difference > (50*24*60*60)){
          //More than 50 days -- summarize 8 hourly
          this.Bins = parseInt(Difference / (8*60*60))+2;
          this.Binsize = "8 Hours";
          this.Span = "8h";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else if(Difference > (14*24*60*60)){
          //More than 14 days -- summarize 4 hourly
          this.Bins = parseInt(Difference / (4*60*60))+2;
          this.Binsize = "4 Hours";
          this.Span = "4h";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else if(Difference > (6*24*60*60)){
          //More than 6 days -- summarize hourly
          this.Bins = parseInt(Difference / (60*60))+2;
          this.Binsize = "One Hour";
          this.Span = "1h";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else if(Difference > (2*24*60*60)){
          //More than 2 days -- summarize half-hourly
          this.Bins = parseInt(Difference / (30*60))+2;
          this.Binsize = "30 Minutes";
          this.Span = "30m";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }else{
          //Less than 2 days -- summarize to 10 minutes
          this.Bins = parseInt(Difference / (10*60))+2;
          this.Binsize = "10 Minutes";
          this.Span = "10m";
          this.AbsoluteEarliest = earliestEpoch - (earliestEpoch % (24*3600))
          this.AbsoluteLatest = latestEpoch + (24*3600 - (latestEpoch % (24*3600)))
        }

        // Now let's do the index...

        if(Difference > (150*24*60*60)){
          this.ChooseIndex = "my_summary_daily"
          this.ChooseEval = ""
        }else if(Difference > (6*24*60*60)){
          this.ChooseIndex = "my_summary_hourly"
          this.ChooseEval = ""
        }else{
          this.ChooseIndex = "rtbbid"
          this.ChooseEval = " | eval MyField = MyField * 300"
        }  

        this.pushContextToChildren();
      }

      a.getModifiedContext=function(){
        var context=this.getContext();
        context.set("Bins", this.Bins);
        context.set("Binsize", this.Binsize);
        context.set("Span", this.Span);
        context.set("AbsoluteEarliest", this.AbsoluteEarliest);
        context.set("AbsoluteLatest", this.AbsoluteLatest);
        context.set("ChooseIndex", this.ChooseIndex);
        context.set("ChooseEval", this.ChooseEval);
        context.set("SpendGraphType", this.SpendGraphType);
        context.set("FractionOfMonth", this.FractionOfMonth);
        context.set("TimeWindowInSec", this.Difference);
        context.set("ChooseDailyAppend", this.ChooseDailyAppend);
        return context
      }
    })
  })

}

Having Issues?: Let me know in the comments, or contact me from the link above.