Splunk Search

Software Versions not comparing correctly

Abe_T
Explorer

I am sure I am sure I am missing something easy but, for some reason, when I compare these two values (they are in string format from my data) the comparison isn't correct. I don't seem to have this issue when I use this EVAL statement with other versions I have, but for some reason, this comparison just throws it out of whack:

 

 

 

| eval Status=case("21.3.0.44"<"5.5.5.0","Declining","21.3.0.44"="5.5.5.0","Mainstream","21.3.0.44">"5.5.5.0","Emerging")

 

 

The result just shows "21.3.0.44" as always less-than. Please advise if I am missing some caveat I am not aware of. P.S. I tried converting to these to numbers but due to all the decimals in version numbers, the number isn't valid. I suppose I could replace the decimals somehow but thought I would ask first before I try going down this route.

Thanks in Advance!

Labels (1)
0 Karma
1 Solution

bowesmana
SplunkTrust
SplunkTrust

@Abe_T 

Good pick! There was a final case statement missing where a part equals the same part of the other version, in which case State became null, having already been set. Should have been

| makeresults
| eval testVersion=split("5.5.5.0;21.3.0.44;21.1.0.133", ";"), myVersion=split("21.3.0.44;5.5.5.0;4.18.19.216;7.999.1.4;5.5.1.3;7.5.5.0;3.5.5.0;104.99.1.1",";")
| mvexpand myVersion
| mvexpand testVersion
| eval testParts=split(testVersion,"."), myParts=split(myVersion,".")
| foreach 0 1 2 3 [ eval tp=mvindex(testParts,<<FIELD>>), mp=mvindex(myParts,<<FIELD>>), State=case(mp<tp, coalesce(State,"Declining"), mp>tp, coalesce(State, "Emerging"), 1==1, State) ]
| fillnull State value="Mainstream"
| table myVersion testVersion State
| sort testVersion

i.e. add in the final case (1==1, State)

I added your test case in the above, which seems to handle all cases now - so you just need that single foreach line and the fillnull following. (I have removed you 'currentState' var).

 

View solution in original post

0 Karma

PickleRick
SplunkTrust
SplunkTrust

Ahhh. I knew there must be a "splunky" (i.e. non-iterative) version. Took me a while but here it is.

| makeresults 
| eval ver1="1.4.4.1", ver2="1.4.4"
| eval ver1split=split(ver1,"."), ver2split=split(ver2,".")
| eval vercombined=mvzip(ver1split,ver2split)
| eval verdiffs=mvmap(vercombined,tonumber(mvindex(split(vercombined,","),0,0))-tonumber(mvindex(split(vercombined,","),1,1)))
| eval verdiffpos=mvfind(verdiffs,"^[^0]")
| eval verdiffval=mvindex(verdiffs,verdiffpos,verdiffpos)
| eval verdiffsign=case(verdiffval>0,-1,verdiffval<0,1,1=1,0)
| eval verresult=if(isnotnull(verdiffpos),verdiffsign,case(mvcount(ver1split)>mvcount(ver2split),-1,mvcount(ver1split)<mvcount(ver2split),1,1=1,0))

It's ugly as hell but it works for any length of version numbers and doesn't use any iterative features (foreach, map or anything like that). Works only on multivalue fields.

It compares version numbers by finding an index on which one "number" differs from the other. If both are "equal" but one of them has additional components at the end, it's considered a higher version number.

So "1.0.2">"1.0.2", "2.0.1">"1.0.4.2.12", but "1.0.1.2">"1.0.1"

Returns -1 if ver1>ver2, 1 if ver1<ver2 or 0 if they are equal

Two remarks:

1) Can be reworked to use less pipes and less intermediate fields but it'd get sooooo ugly...

2) As a nice side-effect, if you add another base to tonumber() calls, you can compare non-decimal numbered versions.

0 Karma

bowesmana
SplunkTrust
SplunkTrust

String comparisons are always lexographic, i.e. 21 is always less than 5 as the string 2 is less than the string 5. If you were to compare the strings 21 with 05 then 21 would be > than 05.

To handle version number comparisons would need something like this (sets up an example data set then does the comparisons - paste this search into the search window)

| makeresults
| eval testVersion="5.5.5.0", myVersion=split("21.3.0.44;5.5.5.0;4.18.19.216;7.999.1.4;5.5.1.3",";")
| mvexpand myVersion
| eval testParts=split(testVersion,"."), myParts=split(myVersion,".")
| foreach 0 1 2 3 [ eval tp=mvindex(testParts,<<FIELD>>), mp=mvindex(myParts,<<FIELD>>), State=case(mp<tp, coalesce(State,"Declining"), mp>tp, coalesce(State, "Emerging")) ]
| fillnull State value="Mainstream"
| table myVersion testVersion State

i.e 4 test versions. This will

  • split the version into its 4 component parts
  • compare each part from your version to the test version and set the state accordingly
    • Note that this ONLY checks less than and greater than
    • the coalesce statement will ensure that only the first state is recognised
  • finally if no State is set, all parts must have been equal, so it's Mainstream

Note that foreach will perform checks for 4 parts. If your versions have different numbers of parts, then it will have to work differently.

 

0 Karma

Abe_T
Explorer

Thank you so much, this is great! Any thoughts on how to easily do a "05" to "21" comparison? I have to account for these still, even with your great solution, because we are encountering situations where the last part of the version number are misreading still. Is there a way in Splunk to check if it's more than 1 digit string to force it to compare as a number? I'm fairly new to Splunk so I am not sure how that would look.

 

Example of issue (should be "Declining"):

| makeresults
| eval testVersion="21.1.0.133", myVersion=split("5.5.0.144",";")
| mvexpand myVersion
| eval testParts=split(testVersion,"."), myParts=split(myVersion,".")
| foreach 0 1 2 3 [ eval tp=mvindex(testParts,<<FIELD>>), mp=mvindex(myParts,<<FIELD>>), State=case(mp<tp, coalesce(State,"Declining"), mp>tp, coalesce(State, "Emerging")) 
    | eval currentState = if(isnull(State),currentState, State)]
| fillnull currentState value="Mainstream"
| table myVersion testVersion currentState

Thanks in advance

0 Karma

bowesmana
SplunkTrust
SplunkTrust

@Abe_T 

Good pick! There was a final case statement missing where a part equals the same part of the other version, in which case State became null, having already been set. Should have been

| makeresults
| eval testVersion=split("5.5.5.0;21.3.0.44;21.1.0.133", ";"), myVersion=split("21.3.0.44;5.5.5.0;4.18.19.216;7.999.1.4;5.5.1.3;7.5.5.0;3.5.5.0;104.99.1.1",";")
| mvexpand myVersion
| mvexpand testVersion
| eval testParts=split(testVersion,"."), myParts=split(myVersion,".")
| foreach 0 1 2 3 [ eval tp=mvindex(testParts,<<FIELD>>), mp=mvindex(myParts,<<FIELD>>), State=case(mp<tp, coalesce(State,"Declining"), mp>tp, coalesce(State, "Emerging"), 1==1, State) ]
| fillnull State value="Mainstream"
| table myVersion testVersion State
| sort testVersion

i.e. add in the final case (1==1, State)

I added your test case in the above, which seems to handle all cases now - so you just need that single foreach line and the fillnull following. (I have removed you 'currentState' var).

 

0 Karma

PickleRick
SplunkTrust
SplunkTrust

Unfortunately, you're limited to harcoded limit of 4 segments in the version number.

And in case of versions of different length but with one being a prefix of the other (like 1.1.0 and 1.1.0.2) it treats them as equal versions.

0 Karma

Abe_T
Explorer

This works great! I'll mark this as resolved. Just a question on the logic so I understand:

  • You're moving from left to right checking if each element is greater or less than.
  • Then based off the last value of State in the foreach statement, you are determining if the whole thing is Emerging or Declining?

Let me know if I am misunderstanding.

Thanks in advance.

 

 

0 Karma
Get Updates on the Splunk Community!

Join Us for Splunk University and Get Your Bootcamp Game On!

If you know, you know! Splunk University is the vibe this summer so register today for bootcamps galore ...

.conf24 | Learning Tracks for Security, Observability, Platform, and Developers!

.conf24 is taking place at The Venetian in Las Vegas from June 11 - 14. Continue reading to learn about the ...

Announcing Scheduled Export GA for Dashboard Studio

We're excited to announce the general availability of Scheduled Export for Dashboard Studio. Starting in ...