[feat] Working example of SimpleXML and SplunkJS interaction.
This commit is contained in:
commit
55d276f41b
|
@ -0,0 +1,4 @@
|
||||||
|
*#
|
||||||
|
.#*
|
||||||
|
*~
|
||||||
|
*.orig
|
|
@ -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
|
|
@ -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);
|
||||||
|
});
|
|
@ -0,0 +1 @@
|
||||||
|
This is where you put any scripts you want to add to this app.
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<nav search_view="search" color="#65A637">
|
||||||
|
<view name="index" default='true' />
|
||||||
|
<view name="search" />
|
||||||
|
<view name="data_models" />
|
||||||
|
<view name="reports" />
|
||||||
|
<view name="alerts" />
|
||||||
|
<view name="dashboards" />
|
||||||
|
</nav>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<panel>
|
||||||
|
<table>
|
||||||
|
<title>Long-running processes</title>
|
||||||
|
<search id="cputimesearch">
|
||||||
|
<query>index=os source=ps | stats latest(cpu_time) by process | sort -latest(cpu_time) | rename latest(cpu_time) as "CPU Time", process as "Process"</query>
|
||||||
|
<earliest>-1h@h</earliest>
|
||||||
|
<latest>now</latest>
|
||||||
|
</search>
|
||||||
|
</table>
|
||||||
|
</panel>
|
|
@ -0,0 +1 @@
|
||||||
|
Add all the views that your app needs in this directory
|
|
@ -0,0 +1,7 @@
|
||||||
|
<dashboard script="title.js">
|
||||||
|
<label>A Dashboard with a Managed Title</label>
|
||||||
|
<description>A simple demonstration of a splunk visualization in which the search is created using SimpleXML, but made accessible to SplunkJS</description>
|
||||||
|
<row>
|
||||||
|
<panel ref="cputime" />
|
||||||
|
</row>
|
||||||
|
</dashboard>
|
|
@ -0,0 +1,4 @@
|
||||||
|
[ui]
|
||||||
|
|
||||||
|
[launcher]
|
||||||
|
version = 0.1
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
||||||
|
[app/ui]
|
||||||
|
version = 6.2.5
|
||||||
|
modtime = 1441563691.674067000
|
||||||
|
|
||||||
|
[app/launcher]
|
||||||
|
version = 6.2.5
|
||||||
|
modtime = 1441563691.676824000
|
|
@ -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
|
||||||
|
[[<dashboard>]] or [[<form>]] 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
|
||||||
|
[[<panel>]], [[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 [[<<this>>]], 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/]].
|
||||||
|
|
||||||
|
<<index.xml>>=
|
||||||
|
<dashboard script="title.js">
|
||||||
|
<label>A Dashboard with a portable Panel and a Managed Title</label>
|
||||||
|
<description>A simple demonstration integrating SimpleXML and SplunkJS</description>
|
||||||
|
<row>
|
||||||
|
<panel ref="cputime" />
|
||||||
|
</row>
|
||||||
|
</dashboard>
|
||||||
|
@
|
||||||
|
|
||||||
|
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.
|
||||||
|
<<cputime.xml>>=
|
||||||
|
<panel>
|
||||||
|
<table>
|
||||||
|
<title>Long-running processes</title>
|
||||||
|
<search id="cputimesearch">
|
||||||
|
<query>index=os source=ps | stats latest(cpu_time) by process | sort -latest(cpu_time)</query>
|
||||||
|
<earliest>-1h@h</earliest>
|
||||||
|
<latest>now</latest>
|
||||||
|
</search>
|
||||||
|
</table>
|
||||||
|
</panel>
|
||||||
|
@
|
||||||
|
|
||||||
|
The [[<dashboard>]] 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:
|
||||||
|
|
||||||
|
<<title.js>>=
|
||||||
|
require([
|
||||||
|
"splunkjs/ready!",
|
||||||
|
"splunkjs/mvc/searchmanager",
|
||||||
|
"underscore",
|
||||||
|
"jquery"
|
||||||
|
], function(mvc, searchManager, _, $) {
|
||||||
|
|
||||||
|
var registry = mvc.Components;
|
||||||
|
|
||||||
|
<<update the title with the data>>
|
||||||
|
|
||||||
|
<<listen to the search for the data>>
|
||||||
|
|
||||||
|
<<find the search>>
|
||||||
|
|
||||||
|
<<wait for the search to be available>>
|
||||||
|
});
|
||||||
|
@
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<<wait for the search to be available>>=
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<<find the search>>=
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<<listen to the search for the data>>=
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<<update the title with the data>>=
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<<update navigation>>=
|
||||||
|
<view name="index" default='true' />
|
||||||
|
<view name="search" />
|
||||||
|
@
|
||||||
|
|
||||||
|
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}
|
Binary file not shown.
Loading…
Reference in New Issue