Zabbix之通过Graphid下载图片——第二版

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zabbix之通过Graphid下载图片——第二版相关的知识,希望对你有一定的参考价值。

阅读本篇之前要先读上文:https://blog.51cto.com/181647568/2480079

根据上一次的Powershell版本的图片下载脚本之后,我收到了一些反馈。
1.上一个脚本通过dll实现访问数据库,通过sql语句来获取graphid,这就意味着除了脚本之外还要有一个dll。而且数据库必须开放访问权限,这就意味着安全性降低。而且并不是所有的生产环境都可以有机会进数据,或者修改数据库的。
2.cookie需要手动在浏览器里抓包获取。这对一些IT来说有难度。
3.itemkey是写死的,如果要多itemkey的图片需要运行多次脚本,或者修改脚本代码。

根据我一开始看的博客中的内容获得启发,我打算做一下改进。
参考文档:https://www.cnblogs.com/dreamer-fish/p/5485556.html
由于生产环境的windows上没有python,我还不能装。但是Linux上的python有不能用那个博文中的代码,缺少一些模块。所以这次我打算用vbs去实现代码。powershell的cookie,session之类的对象太复杂了。

我没有在zabbix查看graph的时候通过http抓包看到graphid产生的过程。我发现zabbix有一套独立的webapi,通过jsonrpc格式数据进行通信,但是在网页正常访问的时候是不用这套api的。
官方文档:https://www.zabbix.com/documentation/3.4/zh/manual/api

api的查询方法和sql是一样的。通过zabbix_agent_host查到hostid,根据hostid和itemkey查到itemid,再通过Itemid查到graphid。然后下载图片。这个jsonrpc的过程不需要用到cookie。而下图片的过程需要cookie。
唯一的不同是jsonrpc webapi需要取到一个authcode的过程,需要用到用户名和密码来获取,接下来的过程都需要用到这个authcode。

为了获取cookie,可能需要模拟一次登录的过程来取到cookie。登录界面index.php中通过post用户名和密码可以获取到cookie。这样我们在代码中只需要填入用户名和密码,而不是cookie,更加的友善了。

以下是代码:

On Error Resume Next

Class VbsJson
    ‘Author: Demon
    ‘Date: 2012/5/3
    ‘Website: http://demon.tw
    Private Whitespace, NumberRegex, StringChunk
    Private b, f, r, n, t

    Private Sub Class_Initialize
        Whitespace = " " & vbTab & vbCr & vbLf
        b = ChrW(8)
        f = vbFormFeed
        r = vbCr
        n = vbLf
        t = vbTab

        Set NumberRegex = New RegExp
        NumberRegex.Pattern = "(-?(?:0|[1-9]d*))(.d+)?([eE][-+]?d+)?"
        NumberRegex.Global = False
        NumberRegex.MultiLine = True
        NumberRegex.IgnoreCase = True

        Set StringChunk = New RegExp
        StringChunk.Pattern = "([sS]*?)([""\x00-x1f])"
        StringChunk.Global = False
        StringChunk.MultiLine = True
        StringChunk.IgnoreCase = True
    End Sub

    ‘Return a JSON string representation of a VBScript data structure
    ‘Supports the following objects and types
    ‘+-------------------+---------------+
    ‘| VBScript          | JSON          |
    ‘+===================+===============+
    ‘| Dictionary        | object        |
    ‘+-------------------+---------------+
    ‘| Array             | array         |
    ‘+-------------------+---------------+
    ‘| String            | string        |
    ‘+-------------------+---------------+
    ‘| Number            | number        |
    ‘+-------------------+---------------+
    ‘| True              | true          |
    ‘+-------------------+---------------+
    ‘| False             | false         |
    ‘+-------------------+---------------+
    ‘| Null              | null          |
    ‘+-------------------+---------------+
    Public Function Encode(ByRef obj)
        Dim buf, i, c, g
        Set buf = CreateObject("Scripting.Dictionary")
        Select Case VarType(obj)
            Case vbNull
                buf.Add buf.Count, "null"
            Case vbBoolean
                If obj Then
                    buf.Add buf.Count, "true"
                Else
                    buf.Add buf.Count, "false"
                End If
            Case vbInteger, vbLong, vbSingle, vbDouble
                buf.Add buf.Count, obj
            Case vbString
                buf.Add buf.Count, """"
                For i = 1 To Len(obj)
                    c = Mid(obj, i, 1)
                    Select Case c
                        Case """" buf.Add buf.Count, """"
                        Case ""  buf.Add buf.Count, "\"
                        Case "/"  buf.Add buf.Count, "/"
                        Case b    buf.Add buf.Count, ""
                        Case f    buf.Add buf.Count, "f"
                        Case r    buf.Add buf.Count, "
"
                        Case n    buf.Add buf.Count, "
"
                        Case t    buf.Add buf.Count, "	"
                        Case Else
                            If AscW(c) >= 0 And AscW(c) <= 31 Then
                                c = Right("0" & Hex(AscW(c)), 2)
                                buf.Add buf.Count, "u00" & c
                            Else
                                buf.Add buf.Count, c
                            End If
                    End Select
                Next
                buf.Add buf.Count, """"
            Case vbArray + vbVariant
                g = True
                buf.Add buf.Count, "["
                For Each i In obj
                    If g Then g = False Else buf.Add buf.Count, ","
                    buf.Add buf.Count, Encode(i)
                Next
                buf.Add buf.Count, "]"
            Case vbObject
                If TypeName(obj) = "Dictionary" Then
                    g = True
                    buf.Add buf.Count, "{"
                    For Each i In obj
                        If g Then g = False Else buf.Add buf.Count, ","
                        buf.Add buf.Count, """" & i & """" & ":" & Encode(obj(i))
                    Next
                    buf.Add buf.Count, "}"
                Else
                    Err.Raise 8732,,"None dictionary object"
                End If
            Case Else
                buf.Add buf.Count, """" & CStr(obj) & """"
        End Select
        Encode = Join(buf.Items, "")
    End Function

    ‘Return the VBScript representation of ``str(``
    ‘Performs the following translations in decoding
    ‘+---------------+-------------------+
    ‘| JSON          | VBScript          |
    ‘+===============+===================+
    ‘| object        | Dictionary        |
    ‘+---------------+-------------------+
    ‘| array         | Array             |
    ‘+---------------+-------------------+
    ‘| string        | String            |
    ‘+---------------+-------------------+
    ‘| number        | Double            |
    ‘+---------------+-------------------+
    ‘| true          | True              |
    ‘+---------------+-------------------+
    ‘| false         | False             |
    ‘+---------------+-------------------+
    ‘| null          | Null              |
    ‘+---------------+-------------------+
    Public Function Decode(ByRef str)
        Dim idx
        idx = SkipWhitespace(str, 1)

        If Mid(str, idx, 1) = "{" Then
            Set Decode = ScanOnce(str, 1)
        Else
            Decode = ScanOnce(str, 1)
        End If
    End Function

    Private Function ScanOnce(ByRef str, ByRef idx)
        Dim c, ms

        idx = SkipWhitespace(str, idx)
        c = Mid(str, idx, 1)

        If c = "{" Then
            idx = idx + 1
            Set ScanOnce = ParseObject(str, idx)
            Exit Function
        ElseIf c = "[" Then
            idx = idx + 1
            ScanOnce = ParseArray(str, idx)
            Exit Function
        ElseIf c = """" Then
            idx = idx + 1
            ScanOnce = ParseString(str, idx)
            Exit Function
        ElseIf c = "n" And StrComp("null", Mid(str, idx, 4)) = 0 Then
            idx = idx + 4
            ScanOnce = Null
            Exit Function
        ElseIf c = "t" And StrComp("true", Mid(str, idx, 4)) = 0 Then
            idx = idx + 4
            ScanOnce = True
            Exit Function
        ElseIf c = "f" And StrComp("false", Mid(str, idx, 5)) = 0 Then
            idx = idx + 5
            ScanOnce = False
            Exit Function
        End If

        Set ms = NumberRegex.Execute(Mid(str, idx))
        If ms.Count = 1 Then
            idx = idx + ms(0).Length
            ScanOnce = CDbl(ms(0))
            Exit Function
        End If

        Err.Raise 8732,,"No JSON object could be ScanOnced"
    End Function

    Private Function ParseObject(ByRef str, ByRef idx)
        Dim c, key, value
        Set ParseObject = CreateObject("Scripting.Dictionary")
        idx = SkipWhitespace(str, idx)
        c = Mid(str, idx, 1)

        If c = "}" Then
            Exit Function
        ElseIf c <> """" Then
            Err.Raise 8732,,"Expecting property name"
        End If

        idx = idx + 1

        Do
            key = ParseString(str, idx)

            idx = SkipWhitespace(str, idx)
            If Mid(str, idx, 1) <> ":" Then
                Err.Raise 8732,,"Expecting : delimiter"
            End If

            idx = SkipWhitespace(str, idx + 1)
            If Mid(str, idx, 1) = "{" Then
                Set value = ScanOnce(str, idx)
            Else
                value = ScanOnce(str, idx)
            End If
            ParseObject.Add key, value

            idx = SkipWhitespace(str, idx)
            c = Mid(str, idx, 1)
            If c = "}" Then
                Exit Do
            ElseIf c <> "," Then
                Err.Raise 8732,,"Expecting , delimiter"
            End If

            idx = SkipWhitespace(str, idx + 1)
            c = Mid(str, idx, 1)
            If c <> """" Then
                Err.Raise 8732,,"Expecting property name"
            End If

            idx = idx + 1
        Loop

        idx = idx + 1
    End Function

    Private Function ParseArray(ByRef str, ByRef idx)
        Dim c, values, value
        Set values = CreateObject("Scripting.Dictionary")
        idx = SkipWhitespace(str, idx)
        c = Mid(str, idx, 1)

        If c = "]" Then
            ParseArray = values.Items
            Exit Function
        End If

        Do
            idx = SkipWhitespace(str, idx)
            If Mid(str, idx, 1) = "{" Then
                Set value = ScanOnce(str, idx)
            Else
                value = ScanOnce(str, idx)
            End If
            values.Add values.Count, value

            idx = SkipWhitespace(str, idx)
            c = Mid(str, idx, 1)
            If c = "]" Then
                Exit Do
            ElseIf c <> "," Then
                Err.Raise 8732,,"Expecting , delimiter"
            End If

            idx = idx + 1
        Loop

        idx = idx + 1
        ParseArray = values.Items
    End Function

    Private Function ParseString(ByRef str, ByRef idx)
        Dim chunks, content, terminator, ms, esc, char
        Set chunks = CreateObject("Scripting.Dictionary")

        Do
            Set ms = StringChunk.Execute(Mid(str, idx))
            If ms.Count = 0 Then
                Err.Raise 8732,,"Unterminated string starting"
            End If

            content = ms(0).Submatches(0)
            terminator = ms(0).Submatches(1)
            If Len(content) > 0 Then
                chunks.Add chunks.Count, content
            End If

            idx = idx + ms(0).Length

            If terminator = """" Then
                Exit Do
            ElseIf terminator <> "" Then
                Err.Raise 8732,,"Invalid control character"
            End If

            esc = Mid(str, idx, 1)

            If esc <> "u" Then
                Select Case esc
                    Case """" char = """"
                    Case ""  char = ""
                    Case "/"  char = "/"
                    Case "b"  char = b
                    Case "f"  char = f
                    Case "n"  char = n
                    Case "r"  char = r
                    Case "t"  char = t
                    Case Else Err.Raise 8732,,"Invalid escape"
                End Select
                idx = idx + 1
            Else
                char = ChrW("&H" & Mid(str, idx + 1, 4))
                idx = idx + 5
            End If

            chunks.Add chunks.Count, char
        Loop

        ParseString = Join(chunks.Items, "")
    End Function

    Private Function SkipWhitespace(ByRef str, ByVal idx)
        Do While idx <= Len(str) And _
            InStr(Whitespace, Mid(str, idx, 1)) > 0
            idx = idx + 1
        Loop
        SkipWhitespace = idx
    End Function

End Class

Set wshnamed=wscript.arguments.named
strGraphID = wshnamed.item("graphid")
strPicSavePath = wshnamed.item("PicSavePath")
strCookies = wshnamed.item("Cookies")

Set fso = CreateObject("Scripting.FileSystemObject")

zabbix_url = "192.1.31.66"
zabbix_index = "http://" & zabbix_url & "/zabbix/index.php"
zabbix_webapi= "http://" & zabbix_url & "/zabbix/api_jsonrpc.php"
zabbix_username = "Admin"
zabbix_password = "zabbix"
Zabbix_cookie = GetZabbixCookie(zabbix_index,zabbix_username,zabbix_password)

If(Zabbix_cookie = "")Then
    Wscript.Echo "Could not get Zabbix cookies, make sure your username and password is correct."
End If

Function GetAuthToken(url,username,password)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1") 
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc" 
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send "{""jsonrpc"": ""2.0"", ""method"": ""user.login"", ""params"":{""user"": """&username&""",""password"": """&password&"""}, ""id"": 0}"

    Set json = New VbsJson
    GetAuthToken = json.Decode(Winhttp.ResponseText)("result")
End Function

Function GetDaySecond(Day)
    GetDaySecond = Day * 24 * 3600
End Function

Function GetGraphid(url,AuthCode,itemid)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc"
    Winhttp.Send "{""jsonrpc"": ""2.0"",""method"": ""graphitem.get"",""params"": {""output"": ""extend"",""expandData"": 1,""itemids"": """&itemid&"""},""auth"": """&AuthCode&""",""id"": 1}"

    Set json = New VbsJson
    GetGraphid = json.Decode(WinHttp.ResponseText)("result")(0)("graphid")
End Function

Function GetHostid(url,AuthCode,zabbix_agent_hostname)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc"
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send "{""jsonrpc"": ""2.0"",""method"": ""host.get"",""params"": {""output"": ""extend"",""filter"": {""host"": """&zabbix_agent_hostname&"""}},""auth"": """&AuthCode&""",""id"": 1}"

    Set json = New VbsJson
    GetHostid = json.Decode(Winhttp.ResponseText)("result")(0)("hostid")

End Function

Function GetItemid(url,AuthCode,hostid,zabbix_item_key)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc"
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send "{""jsonrpc"": ""2.0"",""method"": ""item.get"",""params"": {""output"": ""extend"",""hostids"": """ &hostid & """,""search"":{""key_"": """ & zabbix_item_key & """},""sortfield"": ""name""},""auth"": """&AuthCode&""",""id"": 1}"

    GetItemid = GetMid(Winhttp.ResponseText,"{""itemid"":""",""",""",1)
End Function

Function GetMid(strText, strFormer, strLater,intStartLocation)
    Dim FormerLocation
    Dim LaterLocation
    Dim PFormerLocation
    Dim PLaterLocation

    FormerLocation = InStr(intStartLocation, strText, strFormer)

    If (FormerLocation <> 0) Then
        FormerLocation = FormerLocation + Len(strFormer)
        LaterLocation = InStr(FormerLocation, strText, strLater)
        If (LaterLocation <> 0) Then
            GetMid = Mid(strText, FormerLocation, LaterLocation - FormerLocation)
            Exit Function
        End If
    End If

    GetMid = ""
End Function

Function GetZabbixCookie(zabbix_index,username,password)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", zabbix_index
    Winhttp.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
    Winhttp.Send "name=" & username & "&password=" & password & "&autologin=1&enter=Sign+in"
    GetZabbixCookie = "zbx_sessionid=" & GetMid(winhttp.GetAllResponseHeaders,"zbx_sessionid=",";",1) & ";"
End Function

Sub DownloadZabbixPic(url,strPath,cookie)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1") 
    Winhttp.Open "GET", url
    Winhttp.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send

    Set sGet = CreateObject("ADODB.Stream")
    sGet.Mode = 3
    sGet.Type = 1
    sGet.Open()
    sGet.Write(Winhttp.ResponseBody)
    sGet.SaveToFile strPath
End Sub

AuthCode = GetAuthToken(zabbix_webapi,zabbix_username,zabbix_password)
If AuthCode = "" Then
    Wscript.Echo "Could not get AuthCode."
    Wscript.Quit
End If

CurrentFolder = fso.getfolder(".")
CSV_Path = CurrentFolder&"list.csv"

If (fso.fileExists(CSV_Path)=0) Then
    Wscript.Echo "Could not find " & CSV_Path & "."
    Wscript.Quit
End If

set csv_file = fso.opentextfile(CSV_Path)
csv_text = csv_file.readall
csv_file.close

PicSaveDir = CurrentFolder&""&replace(date(),"/","")

If (fso.folderExists(PicSaveDir)=0) Then
    fso.createfolder(PicSaveDir)
End If

CSV_ReadLine = split(csv_text,vbCrlf)

for i = 1 to ubound(CSV_ReadLine) step 1
    CSV_ReadCol = split(CSV_ReadLine(i),"!")
    Zabbix_agent_host = CSV_ReadCol(0)
    ItemKey = CSV_ReadCol(1)

    PicSaveItemDir = PicSaveDir & "" & Left(ItemKey,4)

    If (fso.folderExists(PicSaveItemDir)=0) Then
        fso.createfolder(PicSaveItemDir)
    End if

    PicSavePath = PicSaveItemDir & "" & Zabbix_agent_host & ".png"

    Hostid = GetHostid(zabbix_webapi,AuthCode,Zabbix_agent_host)
    If (Hostid = "") Then
        Wscript.echo "Hostid is empty. Current host is: " & Zabbix_agent_host
    Else
        ItemID = GetItemid(zabbix_webapi,AuthCode,Hostid,ItemKey)

        If (Itemid = "") Then
            Wscript.echo "Itemid is empty. Current host is: " & Zabbix_agent_host & ". Current item is: "&ItemKey
        Else
            Graphid = GetGraphid(zabbix_webapi,AuthCode,itemid)
            If (graphid = "") Then
                Wscript.echo "Graphid is empty. Current host is: " & Zabbix_agent_host
            Else
                If (fso.fileExists(PicSavePath)) Then
                    Wscript.echo "PNG alreadly exist. " & "Current host is: " & Zabbix_agent_host & ". Current item is: "&ItemKey
                Else
                    DownloadZabbixPic "http://" & zabbix_url & "/zabbix/chart2.php?graphid=" & Graphid & "&period=" & GetDaySecond(30),PicSavePath,Zabbix_cookie
                    Wscript.Echo Zabbix_agent_host & " " & ItemKey &" successfully save as " & PicSavePath
                End if
            End If
        End If
    End If
Next

我来介绍一下此脚本的使用方法。首先开始的第一段设置了这个脚本会在运行出错时,不会退出程序转而继续运行。
然后是一个json的类,用于在vbs中分析json,不过这个json类有点问题,只要json一复杂,就会分析出错。所以如果json很长我会使用另外一个命令去获取json的值。

这个脚本的正常运行还需要一个csv的列表文件来修改我们需要检查的host主机的名字。而且这次我添加了item的项,决定检查host的什么item的续表。其中csv默认的分隔符为“,”,但是我们要改成“!”。因为Itemkey中可能包含逗号,这样子脚本会误把itemkey的值给拆分了。如果同一个主机你需要两种不同的item的图表,你需要写两行。第一行默认是注释,所以是无视掉的。csv文件可以在excel里生成并且修改。也可以在任何的文本编辑器中进行编辑。

这个csv的写法如下:

zabbixAgentHost!itemkey
Zabbix server!system.cpu.utils[,avg]
Zabbix server!vm.memory.useage

这个脚本中变动的部分包括,zabbix的IP地址(对应变量:zabbix_url)、用户名(zabbix_username)、密码(zabbix_password)。还有图表生成php的参数。
图片会默认生成在vbs所在的路径处,先生成一个由今天日期产生的文件夹,再以item名的前4位创建子文件夹,然后图片会放在对应的文件夹中。图片的明明规则是zabbix_agent_hostname的值加.png后缀名。如果图片已经存在,会在运行的时候提示文件已存在,但是不会覆盖已存在的图片。

以下是脚本中function的解释:
GetAuthToken(url,username,password)
通过用户名密码获取webapi authtoken

GetGraphid(url,AuthCode,itemid)
通过itemid获取graphid

GetHostid(url,AuthCode,zabbix_agent_hostname)
通过authcode和zabbix_agent_hostname获取hostid

GetItemid(url,AuthCode,hostid,zabbix_item_key)
通过hostid和itemkey获取itemid

GetMid(strText, strFormer, strLater,intStartLocation)
改编自精易模块的“文本_取中间文本”主要是取出两个字符串中间的结果,用于json不能被正常解析的时候通过文本的形式获取json的值。

GetDaySecond(Day)
计算N天的秒数,这个值用于后期给chart2.php生成表格的时候,提供一个参数的值。

GetZabbixCookie(zabbix_index,username,password)
通过zabbix的用户名和密码来获取cookie。cookie是成功下载到正确的图片的必要条件!

DownloadZabbixPic(url,strPath,cookie)
事实上这个函数并不仅仅能够获取图片,它是将网页返回的内容以二进制的形式保存为一个文件。其中还能使用到cookie。

脚本会读取list.csv文件中的信息(一定要同目录下有一个list.csv文件)然后将它们转换成图片。
注意的是图片生成的参数,你可以搜索脚本中的DownloadZabbixPic来看到zabbix生成图表的参数。主要有四个参数比较重要,stime代表起始时间用法是(yyyyMMDDhhmmss)不填会默认为过去对应时长到现在当前的数据,period代表时长单位是秒可以用getdaysecond来计算出秒数,默认是30天的数据,如果想改可以自己修改,不填这个数据也是默认30天数据。还有两个值是weight和height,这两个值分别控制图片的长和宽。不填也可以。

请在cmd里使用cscript去运行这个vbs,否则报错和提示信息会以信息框的形式出现。你会被不停的弹框烦死。你可以将cscript的命令重定向到一个log里,作为这个脚本的日志文件。

以上是关于Zabbix之通过Graphid下载图片——第二版的主要内容,如果未能解决你的问题,请参考以下文章

使用zabbix邮件发送报表

Python 核心编程 (第二版) 中文高清pdf版110M高清下载

软考之系统集成项目管理工程师(包含2009-2018历年真题详解+第二版考试辅导教程+官方指定最新版教程)

04《软件构架实践第二版》阅读笔记之四

《Effective Java(中文第二版)》PDF下载

Zabbix监控之使用Zabbix监控windows主机