Here's your base search...
index=nitros_servers sourcetype=_json OR sourcetype=xs_json host=isp* "hdr.appName"=""
| fillnull value="NA" hdr.macaddress
| bucket _time span=1d
This section converts the hdr.appVersion to a format that can be directly compared
| eval appVersion=hdr.appVersion
| rex field=appVersion max_match=4 "((?<vnode>\d*)\.?)"
| eval vnode2=tostring(tonumber(mvindex(vnode,0))+4096,"hex")
| eval vnode2=if(mvcount(vnode)<=1,vnode2,mvappend(vnode2,tostring(tonumber(mvindex(vnode,1))+4096,"hex")))
| eval vnode2=if(mvcount(vnode)<=2,vnode2,mvappend(vnode2,tostring(tonumber(mvindex(vnode,2))+4096,"hex")))
| eval vnode2=if(mvcount(vnode)<=3,vnode2,mvappend(vnode2,tostring(tonumber(mvindex(vnode,3))+4096,"hex")))
| nomv vnode2
| rex mode=sed field=vnode2 "s/\s/_/g"
| eval appVersion=vnode2." ".myversion
This section calculates what the max appVersion was on that date at that location, and eliminates all records with the highest version, thus also eliminating any locations without conflict on that day. Note that version_conflict_count is the total number of distinct appVersions on that day at that location that are NOT the highest appVersion.
| eventstats max(appVersion) as maxVersion by hdr.locId hdr.appName _time
| where appVersion!=maxVersion
| stats dc(appVersion) as version_conflict_count, max(maxVersion) as maxVersion, list(appVersion) as appVersion, list(hdr.macaddress) as macaddress by hdr.locId hdr.appName _time
Now we format and list the Dates, the macaddresses and appVersions in play at a location for an app, then get rid of the hex-sort-prefix on the appVersion. appVersion will be in ascending order by version, and version will be presented conventionally.
| eval Date=strftime(_time,"%Y-%m-%d")
| stats values(appVersion) as Versions, count as Days, values(Date) as Date, values(macaddress) as macaddress by hdr.locId hdr.appName
| rex mode=sed field=appVersion "s/(0x[0-9a-fA-F]{4}_?)+\s+?//g"
and present the results
| sort Days desc
| table hdr.locId macaddress hdr.appName Versions Date
However, the max appVersion in play at a particular location may change over time, so I'd suggest something like this to give you all conflicts broken out by what they were conflicting against, and when.
index=nitros_servers sourcetype=_json OR sourcetype=xs_json host=isp* "hdr.appName"=""
| fillnull value="NA" hdr.macaddress
| bucket _time span=1d
| eval appVersion=hdr.appVersion
| rex field=appVersion max_match=4 "((?<vnode>\d*)\.?)"
| eval vnode2=tostring(tonumber(mvindex(vnode,0))+4096,"hex")
| eval vnode2=if(mvcount(vnode)<=1,vnode2,mvappend(vnode2,tostring(tonumber(mvindex(vnode,1))+4096,"hex")))
| eval vnode2=if(mvcount(vnode)<=2,vnode2,mvappend(vnode2,tostring(tonumber(mvindex(vnode,2))+4096,"hex")))
| eval vnode2=if(mvcount(vnode)<=3,vnode2,mvappend(vnode2,tostring(tonumber(mvindex(vnode,3))+4096,"hex")))
| nomv vnode2
| rex mode=sed field=vnode2 "s/\s/_/g"
| eval appVersion=vnode2." ".myversion
| eventstats max(appVersion) as maxVersion by hdr.locId hdr.appName _time
| where appVersion!=maxVersion
| eventstats max(appVersion) as maxVersionApp by hdr.locId hdr.appName
| stats dc(appVersion) as version_conflict_count, max(maxVersion) as maxVersion, list(appVersion) as appVersion, list(hdr.macaddress) as macaddress by hdr.locId hdr.appName _time
| eval Date=strftime(_time,"%Y-%m-%d")
| stats values(appVersion) as Versions, count as Days, values(Date) as Date, max(Date) as LastDate, values(macaddress) as macaddress by hdr.locId hdr.appName maxVersion
| eventstats sum(Days) as totalDays by hdr.locId hdr.appName
| rex mode=sed field=appVersion "s/(0x[0-9a-fA-F]{4}_?)+\s+?//g"
| sort 0 -totalDays -Lastdate
| table hdr.locId macaddress hdr.appName Versions Date Days totalDays
You could also include something like this after the second eventstats , if you only wanted to see conflicts against the most recent appVersion in a location.
| where maxVersion=maxVersionApp
Here's a chunk of sample code for you. (it has already been incorporated above.) It's inelegant, but it will convert the version numbers (up to 4 nodes) to a string that can be directly compared. The number 4096 is being added to make sure the hex translations of the node numbers all have the same length. (The code will fail if a version/level/node goes over 61440.)
| makeresults | eval myversion="16.10.0.7 17.1.0.8" |makemv myversion| mvexpand myversion
| rex field=myversion max_match=4 "((?<versionnode>\d*)\.?)"
| eval versionnode2=tostring(tonumber(mvindex(versionnode,0))+4096,"hex")
| eval versionnode2=if(mvcount(versionnode)<=1,versionnode2,mvappend(versionnode2,tostring(tonumber(mvindex(versionnode,1))+4096,"hex")))
| eval versionnode2=if(mvcount(versionnode)<=2,versionnode2,mvappend(versionnode2,tostring(tonumber(mvindex(versionnode,2))+4096,"hex")))
| eval versionnode2=if(mvcount(versionnode)<=3,versionnode2,mvappend(versionnode2,tostring(tonumber(mvindex(versionnode,3))+4096,"hex")))
| nomv versionnode2
... View more