Introduction

In this post we’ll show off the two types of “persistent variables” in eval – variables and maps.

But first, what the heck is a persistent variable?

Persistent variables

Persistent variables are a special type of variable in eval because they persist between executions of the eval module. In a normal query that uses eval, the eval module executes once for every entry that transits the pipeline. For example:

tag=gravwell eval foo = "bar";

In the above example, if there are 100 entries in the gravwell tag, the eval module will execute the program foo = "bar"; 100 times, once for each entry. In addition, state doesn’t normally carry over between executions of the same eval program.

Enter persistent variables. When you create a persistent variable in eval, the variable and it’s value will “persist” across executions of a given eval program. We can use this feature to execute eval programs that operate across entries, instead of just on one.

An example

Let’s illustrate both types of persistent variables in eval by implementing an equivalent of the count feature in the stats module. If you wanted to get a total count of all entries, as well as a count of entries by each “Appname” in a syslog entry, you would probably do something like this:

tag=gravwell syslog Appname
| stats count by Appname count as total

This works great, so naturally let’s do this using eval instead. I’ll post the entire query first, and then we’ll break it down:

tag=gravwell syslog Appname
| eval

var total=0;
map counts;

total++;
if (!has_key(counts, Appname)) {
    counts[Appname] = 1;
} else {
    counts[Appname] = counts[Appname]+1;
}

output_total = total;
output_counts = counts;
| sort by time asc
| last
| table output_total output_counts

Declaring persistent variables

var total=0;
map counts;

The first lines of the eval module are where the magic happens. By creating a variable “total” and prefixing the declaration with var, we declare that this variable should be persistent. Any eval variable type can be a persistent variable. In this example, we create an integer, initializing it to 0. Initialization isn’t required.

The second line declares a “map”. Just like the “var” above, a “map” is persistent, and acts as a key/value store of variables. A map’s key is a string, and the value is any variable (except another map). Maps don’t ever get initialized.

Building totals

total++;
if (!has_key(counts, Appname)) {
    counts[Appname] = 1;
} else {
    counts[Appname] = counts[Appname]+1;
}

Here we simply use the persistent variable “total” like we would any other, and increment it, once for every execution of the program, which corresponds to the number of entries in the pipeline.

The counts map is used by referencing the key, in square brackets. If a given key doesn’t exist (using the has_key built-in function), we create it and assign it a value of 1, and if it does exist, we simply increment it and store it back to the map.

Outputs

output_total = total;
output_counts = counts;
| sort by time asc
| last
| table output_total output_counts

Persistent variables have another special quality – they are never attached to an entry as an enumerated value like regular variables are. In order to use a persistent variable outside of the eval program it was declared in, you have to assign it to another, non-persistent variable. We do that here with the output_total and output_counts variables.

Finally, we just take the last entry, which corresponds to the last execution of the eval program, and check our results:

table output

Success!

Caveats

Persistent variables have one downside – they “collapse” the query pipeline. This means that if you’re using a Gravwell cluster, any use of a persistent variable will cause data to be funneled to the webserver at that point in the query. This can have negative performance impacts, so write your queries carefully!