commit 55d276f41b76d6833c8539262e252821e86d8977 Author: Elf M. Sternberg Date: Sun Sep 6 19:59:31 2015 -0700 [feat] Working example of SimpleXML and SplunkJS interaction. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f55f75d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*# +.#* +*~ +*.orig diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9c9fd1 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +### This Code Is... + +This repo is the sample code from the tutorial [Referencing SimpleXML +Searches From SplunkJS In Remote +Panels](http://elfsternberg.com/2015/09/06/splunk-splunkjs-simplexml1). +You can either read that tutorial or find the notes in this directory +in PDF form. + +### Setup + +Move or unzip this directory, in its entirely, to your Splunk +instance's apps directory. Restart Splunk. It should just work. + +### Requirements + +This was tested with Splunk 6.2. It's not likely to work with earlier +versions that don't support <panel ref="panelname"> syntax. + +### Documentation + +If you got this, you must have followed one of the tutorial links. +If you didn't, this is the sample code for the \ No newline at end of file diff --git a/appserver/static/title.js b/appserver/static/title.js new file mode 100644 index 0000000..4504048 --- /dev/null +++ b/appserver/static/title.js @@ -0,0 +1,41 @@ +require([ + "splunkjs/ready!", + "splunkjs/mvc/searchmanager", + "underscore", + "jquery" +], function(mvc, searchManager, _, $) { + var registry = mvc.Components; + + var updateTitle = function(manager, data) { + if ( !data || !data.results || !data.results.length) { + return; + } + + var topprocess = data.results[0]; + + $("[data-panel-ref=cputime] .panel-head h3") + .text("Longest Running Process: " + topprocess["Process"] + + " (" + topprocess["CPU Time"] + ")"); + }; + + var setUpSearchListener = function(searchname) { + var searchmanager = registry.getInstance(searchname); + var resultmanager = searchmanager.data("preview", { + output_mode: "json", + count: 1, + offset: 0 + }); + resultmanager.on("data", updateTitle); + }; + + var findPanel = function() { + var panel = _.filter(registry.getInstanceNames(), + function(name) { return name.match(/panel\d+_cputimesearch/); }); + if (panel.length === 1) { + registry.off('change', findPanel); + setUpSearchListener(panel[0]); + } + }; + + var handle = registry.on('change', findPanel); +}); diff --git a/bin/README b/bin/README new file mode 100644 index 0000000..9a70db0 --- /dev/null +++ b/bin/README @@ -0,0 +1 @@ +This is where you put any scripts you want to add to this app. diff --git a/default/app.conf b/default/app.conf new file mode 100644 index 0000000..81f5117 --- /dev/null +++ b/default/app.conf @@ -0,0 +1,16 @@ +# +# Splunk app configuration file +# + +[install] +is_configured = 0 + +[ui] +is_visible = 1 +label = Search Handle + +[launcher] +author = Elf M. Sternberg +description = Search Handle demonstrates how to find a search defined in SimpleXML and manipulate it via SplunkJS. There are a lot of demonstrations showing how to build a search in SplunkJS and reference it in SimpleXML, but not the other way aronud. +version = 1.0 + diff --git a/default/data/ui/nav/default.xml b/default/data/ui/nav/default.xml new file mode 100644 index 0000000..d3e3221 --- /dev/null +++ b/default/data/ui/nav/default.xml @@ -0,0 +1,8 @@ + diff --git a/default/data/ui/panels/cputime.xml b/default/data/ui/panels/cputime.xml new file mode 100644 index 0000000..e9b997b --- /dev/null +++ b/default/data/ui/panels/cputime.xml @@ -0,0 +1,10 @@ + + + Long-running processes + + index=os source=ps | stats latest(cpu_time) by process | sort -latest(cpu_time) | rename latest(cpu_time) as "CPU Time", process as "Process" + -1h@h + now + +
+
diff --git a/default/data/ui/views/README b/default/data/ui/views/README new file mode 100644 index 0000000..6cf74f0 --- /dev/null +++ b/default/data/ui/views/README @@ -0,0 +1 @@ +Add all the views that your app needs in this directory diff --git a/default/data/ui/views/index.xml b/default/data/ui/views/index.xml new file mode 100644 index 0000000..ba273a0 --- /dev/null +++ b/default/data/ui/views/index.xml @@ -0,0 +1,7 @@ + + + A simple demonstration of a splunk visualization in which the search is created using SimpleXML, but made accessible to SplunkJS + + + + diff --git a/local/app.conf b/local/app.conf new file mode 100644 index 0000000..d49ee2b --- /dev/null +++ b/local/app.conf @@ -0,0 +1,4 @@ +[ui] + +[launcher] +version = 0.1 diff --git a/metadata/default.meta b/metadata/default.meta new file mode 100644 index 0000000..b77b8cb --- /dev/null +++ b/metadata/default.meta @@ -0,0 +1,35 @@ + +# Application-level permissions + +[] +access = read : [ * ], write : [ admin, power ] + +### EVENT TYPES + +[eventtypes] +export = system + + +### PROPS + +[props] +export = system + + +### TRANSFORMS + +[transforms] +export = system + + +### LOOKUPS + +[lookups] +export = system + + +### VIEWSTATES: even normal users should be able to create shared viewstates + +[viewstates] +access = read : [ * ], write : [ * ] +export = system diff --git a/metadata/local.meta b/metadata/local.meta new file mode 100644 index 0000000..d2d6123 --- /dev/null +++ b/metadata/local.meta @@ -0,0 +1,7 @@ +[app/ui] +version = 6.2.5 +modtime = 1441563691.674067000 + +[app/launcher] +version = 6.2.5 +modtime = 1441563691.676824000 diff --git a/simplesplunkjs.nw b/simplesplunkjs.nw new file mode 100644 index 0000000..800a8d0 --- /dev/null +++ b/simplesplunkjs.nw @@ -0,0 +1,275 @@ +% -*- Mode: noweb; noweb-code-mode: javascript-mode ; noweb-doc-mode: latex-mode -*- +\documentclass{article} +\usepackage{noweb} +\usepackage{hyperref} +\begin{document} + +% Generate code and documentation with: +% +% noweave -filter l2h -delay -x -html simplesplunkjs.nw | htmltoc > simplesplunkjs.html +% notangle -Rtitle.js simplesplunkjs.nw > title.js +% notangle -Rindex.xml simplesplunkjs.nw > index.xml +% notangle -Rcputime.xml simplesplunkjs.nw > cputime.xml + +\section{Introduction} + +Splunk's SimpleXML is an XML file format to describe a custom dashboard +with searches, inputs and panels. There are a number of +\nwanchorto{href="http://dev.splunk.com/getstarted}{fantastic resources} +for building them, but I recently encountered an interesting problem. +That link also discusses SplunkJS, a Javascript library that allows +users to customize searches and visualizations far beyond what SimpleXML +allows. + +SplunkJS is usually used with raw HTML and CSS, but can be pulled into a +SimpleXML file by uing the [[script]] attribute in the SimpleXML opening +[[]] or [[
]] tag. It's easy to make a SplunkJS search +and attach it to a SimpleXML visualization; it's not so easy to make a +SimpleXML search and attach it to a SplunkJS visualization. This +document shows you how, and shows you how to fix a peculiarity that +arises from creating a well-organized ecosystem of panels and +dashboards. + +In later versions of Splunk, SimpleXML has a new attribute for +[[]], [[ref]], which allows you to define a panel in a single +file and drop it into a number of different dashboards without having to +cut-and-paste the panel code. In the process, SimpleXML mangles the +names of searches and visualizations, and so finding and manipulating +those searches has become difficult. + +This example uses the Splunk Unix TA (Technology Add-on), so you should +download and install that. What data you use isn't really important. +For our example, though, we're going to do is create a single dashboard +with a single independent panel that shows the list of processes running +on a host, find that panel, find it's search, find it's title, and +modify the title with the name of the longest-running process. + +After installing Splunk (here's the +\nwanchorto{http://www.splunk.com/en_us/download/splunk-enterprise.html}{free + version of the Enterprise Edition}, limited to a half-GB of data per +day) and getting it up and running, click on the App icon (the gear +symbol) on the left sidebar. On the Applications list, click on "Create +a New App", and provide it with a name, a directory slug, and a version +number. + +Now it's time to fire up your editor. We need to create three things. +A dashboard, a panel, and a javascript file to perform the magic. + +\subsection{Literate Program} + +A note: this article was written with the +\nwanchorto{http://en.wikipedia.org/wiki/Literate_programming}{Literate + Programming} toolkit +\nwanchorto{http://www.cs.tufts.edu/~nr/noweb/}{Noweb}. Where you see +something that looks like [[<>]], it's a placeholder for code +described elsewhere in the document. Placeholders with an equal sign +at the end of them indicate the place where that code is defined. The +link (U->) indicates that the code you're seeing is used later in the +document, and (<-U) indicates it was used earlier but is being defined +here. + +\section{The Dashboard Files} + +The Dashboard file is simple. We just want to pull in a panel. This +goes into [[APP\_HOME/default/data/ui/views/index.xml]]. Here, +APP\_HOME is the path to the directory slug where your app is stored. I +install Splunk in [[/opt]] and I named my example "searchhandle," +thus the path is +[[/opt/splunk/etc/apps/searchhandle/default/data/ui/views/]]. + +<>= + + + A simple demonstration integrating SimpleXML and SplunkJS + + + + +@ + +The panel file is also simple. It's going to define a search and a +table. It goes in +[[APP\_HOME/default/data/ui/panels/cputime.xml]]. Note that the +filename must match the [[ref]] attribute. I've limited the +search to the last hour, just to keep from beating my poor little laptop +to death. +<>= + + + Long-running processes + + index=os source=ps | stats latest(cpu_time) by process | sort -latest(cpu_time) + -1h@h + now + +
+
+@ + +The [[]] tag in our dashboard file has a +[[script]] attribute. This is where we'll put our logic for +manipulating the title of our panel. It's annoying that we have to put +our script reference in the dashboard and not the panel. It's possible +to have a file named "dashboard.js" which will be loaded for \textit{every} XML +file in your app, and then have it selectively act on panels when they +appear. + +\section{The Javascript} + +Javascript files go in the [[APP\_HOME/appserver/static/]] +directory. I've named ours [[title.js]]. + +Splunk uses the [[require]] facility to import files. In the +prelude to any SplunkJS interface, you must start with the +[[ready!]] import, which doesn't allow the contents of this file +to run until the Splunk MVC (Model View Controller) base library is +loaded. We're also loading the [[searchmanager]] and two utility +libraries: \nwanchorto{http://underscorejs.org/}{underscore} and +\nwanchorto{https://jquery.com/}{jquery}, both of which come with the +SplunkJS UI. + +The one thing we're most concerned with is the [[registry]], +which is a central repository where all components of the current Splunk +job's client-side operations are indexed and managed. + +The file's outline looks like this: + +<>= +require([ + "splunkjs/ready!", + "splunkjs/mvc/searchmanager", + "underscore", + "jquery" +], function(mvc, searchManager, _, $) { + + var registry = mvc.Components; + + <> + + <> + + <> + + <> +}); +@ + +In the outline, we took one of the items passed in, +[[mvc.Components]], and gave it a name, the \textit{registry}. Waiting +for the search to be available is as simple as listening to the +registry: + +<>= + +var handle = registry.on('change', findPanel); +@ + +Finding the search and attaching a listener to it is actually one of the +two hardest parts of this code. First, because we have to \textit{find} it, +and the new panels layout makes that difficult, and secondly, because +the change event mentioned above can happen multiple times, but we want +to make sure we only set up our listener only once. + +Below, the function [[findPanel]] lists through all the Splunk +managed objects on the page, and finds our search. It does this by +looking for a registry name that matches the ID of our search. The +panel layout mangles the name, attaching the prefix ``panelXX\_'' where +XX is some arbitrary index number. (In practice, the index number is +probably deterministic, but that's not useful or important if you're +going to be using this panel on multiple dashboards.) Underscore's +[[filter]] is perfect for finding out if our search is +available. If it is, we disable the registry listener and proceed to +the next step, sending it the search name. + +<>= + +var findPanel = function() { + var panel = _.filter(registry.getInstanceNames(), + function(name) { return name.match(/panel\d+_cputimesearch/); }); + if (panel.length === 1) { + registry.off('change', findPanel); + setUpSearchListener(panel[0]); + } +}; +@ + +This is the most straightforward part of the code. Having found the +search name, we then get the search manager, get its results manager, +and then set up a listener to it that will update the title with the +data. + +Splunk searches manage the \textit{task} of searching, but not the actual +data. That happens in a Result, which updates regularly with the +growing cache of data from the server to the browser. + +This code skips a ton of details, mostly about listening to the search +for failure messages. That's okay. This is just an example, and it +works 99\% of the time anyway. Since we're going to change the title to +include the longest-running process, and our search is pre-sorted, we +just need a count of one. This Result uses the same dataset as the +actual visualization and puts no additional strain on the Splunk server +or bandwidth between the server and the browser. + +<>= + +var setUpSearchListener = function(searchname) { + var searchmanager = registry.getInstance(searchname); + var resultmanager = searchmanager.data("preview", { + output_mode: "json", + count: 1, + offset: 0 + }); + resultmanager.on("data", updateTitle); +}; +@ + +The last thing we do is update the title. (Remember, that's our goal). +The panel's title is found in the [[.panel-head h3]] child +DOM object. Finding the panel is trickier, but Splunk gives us an +attribute with the name of the panel's filename, so jQuery can find it +for us. There's a guard condition to ensure that we actually have some +data to work with. + +The names of the fields correspond to the final names in the search. +I've always found Splunk's naming conventions to be a little fragile, +but it works most of the time. + +<>= + + var updateTitle = function(manager, data) { + if ( !data || !data.results || !data.results.length) { + return; + } + + var topprocess = data.results[0]; + $("[data-panel-ref=cputime] .panel-head h3") + .text("Longest Running Process: " + topprocess["Process"] + + " (" + topprocess["CPU Time"] + ")"); + }; +@ + +\section{Navigation} + +One last detail: You want to be able to get to this page. + +To do that, open the file at: +[[APP\_HOME/default/data/ui/nav/default.xml]] and change the line for +search to look like this: + +<>= + + +@ + +Now restart Splunk + +And that's it. Put it all together, and you've got yourself a working +application in which SplunkJS can tap into SimpleXML searches and +exploit their data, even if that search is defined in an independent +panel. + +This code is available at my github at +\nwanchorto{https://github.com/elfsternberg/Splunk-SimpleXML-SplunkJS}{Splunk + with SimpleXML and Javascript}. + +\end{document} diff --git a/simplesplunkjs.pdf b/simplesplunkjs.pdf new file mode 100644 index 0000000..522c24f Binary files /dev/null and b/simplesplunkjs.pdf differ