暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

官方博文 | 为Zabbix Agent 2 开发插件

Zabbix开源社区 2020-04-12
1092


在此感谢米宏先生对演讲原文的翻译整理。


简介——米宏

曾就职于多家知名互联网企业(盛大、一下科技、新浪微博等)负责运维工作,主要从事系统运维、服务器监控、数据采集、日志处理、故障分析。是Zabbix中国社区的活跃用户之一,有超过1万小时的Zabbix使用、开发经验。





前言

Zabbix Agent 2 是一个很有发展潜力的平台,它为Zabbix 提供了更多的数据采集功能。新一代Agent它使用强大的Go语言进行编写,为插件的开发人员提供了更多的选择,不仅如此,它不会像C那样加载复杂的模块,因此大家可以更轻松的创建插件。





在2019年的Zabbix峰会上,除了介绍Zabbix 4.4之外,我们还介绍了Zabbix Agent 2.与之前的版本不同的是,现在开发人员可以使用Go编写Zabbix插件。许多人会问:“嘿,那我们该怎么做?这样的插件是什么样的?我们在哪里可以找到示例和文档?”

我将在下文中回答这些问题以及其他一些问题, 我们将逐步介绍它们。



1


为什么我们要创建一个新的Zabbix Agent?


如果您曾经尝试或想要为第一个Zabbix Agent编写插件,您可能会注意到有很多限制。


第一个agent的插件可能作为几个独立的进程启动,因此开发人员没有足够的控制权。使用长连接、在检查过程中保存受监控组件的状态以及接收SNMP traps都是很难实现或不可能实现的。


Zabbix Agent 2的体系结构完全不同。它使用Go作为基础,从头开始编写,从第一个Zabbix agent中重用了一些C语言代码,因此开发插件比在C语言中开发插件要容易得多。使用这个新agent(我们称之为Go agent)实现上面列出的功能要方便得多。此外,Go agent在协议、配置和指标方面与经典Zabbix agent向后兼容。

 

这些是Goagent独有的新功能:


•agent和插件配置在一个配置文件中,


•开箱即用的并发检查,


•完全控制数据的收集和导出,


•Windows平台上的插件支持,


•每个插件超时。



新的agent在Zabbix 4.4中作为一个实验特性提供,并将在Zabbix 5.0启动时投入生产。



2


Agent 架构


在我们开发第一个插件之前,让我们先来看看它是如何工作的。请记住,以下信息适用于Zabbix 4.4。考虑到Go agent是实验性的特性,到Zabbix 5.0发布时,其中一些功能可能已经不适用了。


agent的主要三个组件是ServerConnector、ServerListener和Scheduler。


ServerConnector 管理与服务器(接收配置/导出数据)、监控项配置和历史缓存的通信。每个活动服务器有一个Connector。


ServerListener接受来自服务器的被动检查请求,然后将其发送到Scheduler。目前,它只能做这个,但将来可能会有更多的功能。


Scheduler根据计划和并发设置管理任务队列。Go Agent根据项目设置定义的计划启动单个计划程序以管理任务(插件)。


我们可以根据主动检查和被动检查来查看agent的内部结构(即将推出大量被动检查)。请记住,我们在这两种类型之间进行区分只是为了演示agent的工作方式,因为两者在技术上共享相同的组件。


以下方案说明了每种检查类型的组件交互。很抱歉图片质量,我不是PlantUML的专业人士。


Active Checks

对于每个活动服务器,我们都创建ServerConnector和ResultCache,它们都在其自己的goroutine中启动。


Passive Checks

经典被动检查还使用Scheduler来管理任务。但是,它从ServerListener而不是ServerConnector接收项目配置。此外,结果不会被缓存,而是直接发送到Result Writer,后者将请求的数据发送到服务器。



3


配置处理


从Zabbix服务器接收项目配置后,ServerConnector更新其自己的数据并为每个提供相应指标的插件创建一个特定的updateRequest。通过通道将请求发送到Scheduler,然后Scheduler创建任务并将其放入队列。这样,插件将在完成分配给它的所有先前任务后启动这些任务。



4


调度与任务


agent通过两层任务队列与插件交互:

• 每个插件都有一个任务队列;

• Scheduler具有活动的插件队列。


这样可以确保更好的并发性。当由于并发性限制而无法执行任务时,相应的插件将离开Scheduler队列(但任务仍保留在插件队列中),并且仅在下一个任务可以完成时才放回。


一秒钟内完成的任务按以下顺序运行:

1. configuratorTask,

2. starterTask,

3. collectorTask,

4. watcherTask,

5. exporterTask(directExporterTask),

6. stopperTask。


还有一个名为taskBase的任务,其中包含指向插件的链接,计划的完成时间以及其他取决于任务类型的技术数据。


ExporterTask用于主动检查(也将用于即将进行的批量被动检查)。此任务是指必须定期轮询的项目。Scheduler在单独的goroutine中调用Exporter接口的Export()函数,并将结果写入ResultWriter。


directExporterTask用于被动检查。它与exporterTask之间的区别在于,如果轮询指标标准但未获取结果(空值),则该任务将再次放入队列,并且Scheduler将尝试在一秒钟内再次运行它。重复此过程,直到轮询成功或发生超时为止。另一个区别是directExporterTask不允许返回多个值。


WatcherTask包含要监控的指标(请求)列表。Scheduler调用Watcher接口的Watch()函数,并将请求列表作为参数传递。


调度程序调用Collect()
每隔几秒钟就使用一次Collector接口的函数,则确切的间隔由Period()指定。


激活插件后,Scheduler将调用Runner接口的Start()函数。


插件停止时,Scheduler将调用Runner接口的Stop()函数。


Scheduler调用Configurator接口的Configure()函数,并将全局agent选项和具有特定插件选项,作为参数传递给它。




5


接口



总而言之,有五个可用的接口:Exporter,Watcher,Collector,Runner和Configurator。

Exporter和Watcher定义了数据在组件之间的流动方式:Exporter使用拉模型,而Watcher使用推模型。


plugin.Exporter

    type Exporter interface {
    Export(key string, params []string, context ContextProvider) (result interface{}, err error)
    }

    Exporter是一个非常简单的接口,它轮询指标并返回一个值、多个值、一个错误或根本不返回任何内容。它接受一个准备好的键、参数和上下文,这对大多数插件来说已经足够了。请记住,它是唯一允许并发访问的接口。对于其他接口,在插件完成其任务之前,任何其他方法都无法执行,因此,如果插件需要并发访问,请考虑这一点:您应该确保每个线程都可以访问共享数据。幸运的是,Go提供了许多选项:mutexes, channels, atomic counters, sync.Map和其他synchronization primitives。另外,不要忘记使用Race Detector来查找Race 条件。


    Export()函数对每个插件最多可以有100个并发调用。如果需要,您可以使用

    plugin.Base.SetCapacity() function.

      func (b *Base) SetCapacity(capacity int)

      您也可以在配置文件中的相应参数中设置容量。例如:

        Plugins..Capacity=1
        plugin.Watcher

          type Watcher interface {
          Watch(requests []*Request, context ContextProvider)
          }


          使用Watcher,可以在不使用Scheduler的情况下实施指标标准轮询过程。这对于使用捕获并且需要对收集和导出数据进行完全控制的插件很有用。该接口主要用于等待数据,并在接收到数据后将结果发送到服务器。有了它,我们可以实现日志监控或创建一个插件,该插件订阅来自外部源的事件并等待接收数据。

          plugin.Collector

            type Collector interface {
            Collect() error
            Period() int
            }

            Collector用于需要定期收集数据的插件。但是,它无法返回数据,因此您需要使用Exporter。


            Collector 有两个功能:


            •Collect()实现数据收集的逻辑;
            •Period()设置数据收集的时间段。
            该接口可用于直接通过agent收集CPU和HDD数据。

            plugin.Runner
              type Runner interface {
              Start()
              Stop()
              }

              Runner提供了一种在激活插件时执行初始化(Start()函数)和在插件停止时进行取消初始化(Stop()函数)的方法。


              使用此接口,插件可以启动或停止后台线程,释放未使用的资源,关闭连接等。


              插件的激活和停用取决于是否有任何待处理的指标。在主动检查中,当Zabbix服务器或Zabbixagent发送配置更新时,Scheduler接收新任务。如果分配了任务,则激活插件,如果更新后的配置不包含任何请求,插件将停止。在被动检查中,插件在收到来自服务器的请求后即被激活,并在上次请求后24小时后停止。


              plugin.Configurator

                type Configurator interface {


                Configure(globalOptions *GlobalOptions, privateOptions interface{})


                Validate(privateOptions interface{}) error


                }

                Configurator用于配置插件。
                它具有两个功能:
                •Configure()将配置参数加载到开发人员定义的结构中。
                •Validate()检查配置文件中是否有错误。如果发现任何问题,该agent将无法启动,并且我们会收到错误通知。


                我们也不需要做任何事情来读取或解析配置文件,因为agent会处理它。


                Go Agent的配置参数与经典Zabbixagent的配置参数基本一致,但也有一些例外。


                种类

                插件分为外部或内部的。内部插件导出内部agent数据。它们包含在internal/agent程序包中,并且名称中带有plugin_前缀。内部插件的一个示例是处理用户User Parameters.的插件。


                所有其他插件,包括那些收集标准指标(从CPU,硬盘驱动器,网络,内存等)的插件,都是外部插件。我们的插件也将属于此类型,并且可以使用相同的功能。外部插件位于go plugins目录中,每个插件均位于其自己的子目录中。



                6


                Hello, world!


                插件只是一个Go包,带有一个或多个定义其逻辑的接口。这是一个基本插件的示例:

                  package packageName
                  import "zabbix.com/pkg/plugin"
                  type Plugin struct {
                  plugin.Base
                  }
                  var impl Plugin
                  func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (res interface{}, err error) {
                  // Write your code here
                  return
                  }
                  func init() {
                  plugin.RegisterMetrics(&impl, "PluginName", "key", "Description.")
                  }

                  这比bash或python脚本更复杂,不是吗?现在,我们添加一些代码,这些代码将做一些有用的事情。


                  我们将通过编写一个插件来进行练习,该插件向我们显示一个城市的天气,并将城市名称作为参数传递给它。


                  为此,首先下载Zabbixagent程序源代码。

                    $ git clone https://git.zabbix.com/scm/zbx/zabbix.git --depth 1 zabbix-agent2
                    $ cd zabbix-agent2
                    # You can work in the master branch but I recommend creating a separate branch based on one of the stable branches
                    $ git checkout -b feature/myplugin release/4.4


                    让我们创建一个名为src go plugins weather的目录,然后创建一个名为weather.go的空文件,其中将包含我们的代码。


                    接下来,我们导入一个名为zabbix.com/pkg/plugin的内置软件包。

                      package weather
                      import "zabbix.com/pkg/plugin"


                      然后,我们定义我们的结构并在其中包含来自插件包的Base结构。我们稍后会用到它。

                        type Plugin struct {
                        plugin.Base
                        }
                        var impl Plugin

                        现在,让我们编写检索和处理数据的代码。我们只需要:
                        1.发送一个GET请求到天气提供商API(感谢wttr.in!);
                        2.读取结果;
                        3.解决错误;
                        4.并返回结果。


                          func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
                          if len(params) != 1 {
                          return nil, errors.New("Wrong parameters.")
                          }


                          // https://github.com/chubin/wttr.in
                          res, err := p.httpClient.Get(fmt.Sprintf("https://wttr.in/~%s?format=%%t", params[0]))
                          if err != nil {
                          return nil, err
                          }


                          temp, err := ioutil.ReadAll(res.Body)
                          _ = res.Body.Close()
                          if err != nil {
                          return nil, err
                          }


                          return string(temp)[0 : len(temp)-4], nil
                          }


                          下一步是注册指标,让agent知道它们,以便它可以将它们传递给我们的插件进行处理。


                          我们使用plugin.RegisterMetrics()函数。

                            // impl refers to the plugin implementation
                            // name is the name of our plugin
                            // params are metrics and their descriptions (key1, descr1, key2, descr2, keyN, descrN...)
                            func RegisterMetrics(impl Accessor, name string, params ...string)

                            让我们从init()函数调用它(以便在agent启动时执行)。

                              func init() {
                              plugin.RegisterMetrics(&impl, "Weather", "weather.temp", "Returns Celsius temperature.")
                              }


                              通过调用此函数,我们还可以注册多个指标。

                                package plugins 
                                import (
                                _ "zabbix.com/plugins/kernel"
                                _ "zabbix.com/plugins/log"
                                // ...
                                _ "zabbix.com/plugins/weather"
                                )


                                目前仅支持三个平台(Linux,Darwin和Windows),但将来我们可能会添加更多平台。


                                最后,要让agent知道我们的插件,以便在编译期间添加它,我们必须将其包括在src go plugins plugins_ <platform> .go文件导入列表中。



                                7


                                编译我们的插件


                                如果你尚未安装Go,请立即安装。


                                我们需要Go 1.13版或更高版本。


                                要使用随附的插件构建agent,我们只需在配置中添加–enable-agent2选项并运行make命令。

                                  $ cd <zabbix-source>
                                  $ ./bootstrap.sh; ./configure --enable-agent2 --enable-static; make

                                  然后我们将启动agent并检查我们的新插件是如何工作的。我们会把moscow作为参数,然后看看结果。

                                    $ <zabbix-source>/src/go/bin/zabbix_agent2 -t weather.temp[moscow]
                                    +1

                                    你只需要构建一次agent。在进一步的插件开发过程中,我们可以使用go run命令快速检查代码的工作方式。

                                      $ go run <zabbix-source>/src/go/cmd/zabbix_agent2/zabbix_agent2.go



                                      8


                                      日志记录


                                      如果您的插件需要记录日志,则可以使用zabbix.com/pkg/log软件包中的函数:Tracef(),Debugf(),Warningf(),Infof(),Errf(),Critf()。我们的Plugin结构具有相同的功能,它们只是上述功能的包装。唯一的区别是,它们在邮件中添加了[<PluginName>]前缀。



                                      9


                                      插件配置


                                      Go Agent允许我们使用Plugins参数在Agent配置文件中配置插件。与其他agent程序参数不同,它不是参数的键/值类型。这是一个单独的部分,您可以在其中描述每个插件的特定参数。基本上看起来像这样:


                                      Plugins.<PluginName>.<Parameter>=<Value>.此参数有几个规则:


                                      1.建议使用大写的插件名称;

                                      2.参数应大写;

                                      3.不允许使用特殊字符;

                                      4.嵌套不受最高级别的限制;

                                      5.参数个数不限。


                                      您可以使用Configurator将参数传递给插件。举一个例子,我们将在插件中添加一个名为Timeout的参数。它将定义HTTP请求的最长时间。


                                      假设我们希望时间范围介于1到30秒之间。同样,它是可选的,默认情况下等于全局agent超时。


                                      现在,我们可以创建一个描述我们的配置的结构。

                                        type PluginOptions struct {
                                        Timeout is the maximum time for waiting when a request has to be done. Default value equals the global timeout.
                                        Timeout int `conf:"optional,range=1:30"`
                                        }

                                        您可以看到,我们将此参数定义为可选参数,并通过使用conf标签中的元数据来设置可接受的时间范围。agent读取配置文件时可以使用此数据。


                                        元数据格式如下:


                                        •<name>是参数的名称(如果配置文件中的参数名称与相应结构字段的名称不同);

                                        •可选-如果需要将参数定义为可选,则包括此属性;

                                        •<range>是可接受的时间范围,您还可以将最小值和最大值设置为<min>:<max>;

                                        •<default value>是参数的默认值,它们始终是最后一个属性。


                                        现在,通过添加一个字段来存储配置扩展我们的插件结构。另外,我们还将为http.Client添加设置超时时间。

                                          type Plugin struct {
                                          plugin.Base
                                          options PluginOptions
                                          httpClient http.Client
                                          }


                                          然后,我们将实现Configurator。如您所知,它有两种方法,Configure()和Validate()。

                                            func (p *Plugin) Configure(global *plugin.GlobalOptions, privateOptions interface{}) {
                                            if err := conf.Unmarshal(privateOptions, &p.options); err != nil {
                                            p.Errf("cannot unmarshal configuration options: %s", err)
                                            }


                                            Set default value
                                            if p.options.Timeout == 0 {
                                            p.options.Timeout = global.Timeout
                                            }


                                            p.httpClient = http.Client{Timeout: time.Duration(p.options.Timeout) * time.Second}
                                            }


                                            func (p *Plugin) Validate(privateOptions interface{}) error {
                                            Nothing to validate
                                            return nil
                                            }


                                            通过调用conf.Unmarshal()函数,我们可以将插件参数加载到我们创建的结构中。


                                            现在,我们将p.httpClient.Get替换http.Get。

                                              res, err := p.httpClient.Get(fmt.Sprintf("https://wttr.in/~%s?format=%%t", params[0]))
                                              if err != nil {
                                              if err.(*url.Error).Timeout() {
                                              return nil, errors.New("Request timeout.")
                                              }
                                              return nil, err
                                              }


                                              最后,我们可以将参数添加到agent的配置文件中:

                                              Plugins.Weather.Timeout=1


                                              现在,如果超过超时时间,我们的插件将返回错误。

                                              但是,如果我们输入无效值并启动agent,该怎么办?您可以自己尝试一下,看看该agent仍然可以顺利启动。在这种情况下,超时将具有默认值-等于全局超时。


                                              您只会在首次引用插件时(在激活插件并调用Validate()和Configure()时)在日志中看到警告。
                                              确切的说那不是我们想要的。更好的方案是当配置无效时agent崩溃。为此,我们将优化Validate()部分,以便在agent启动时为实现该方法的所有插件调用此方法。

                                                func (p *Plugin) Validate(privateOptions interface{}) error {
                                                var opts PluginOptions
                                                return conf.Unmarshal(privateOptions, &opts)
                                                }

                                                现在,如果我们为参数输入一个无效值并尝试启动agent,我们将得到如下信息:“cannot create scheduling manager: invalid plugin Weather configuration: Cannot assign configuration: invalid parameter Plugins.Weather.Timeout at line 411: value out of range.”


                                                该agent未来版本将允许您即时配置插件。在收到相应的运行时命令时,将调用Validate()和Configure(),您的插件将能够对它们做出反应并更新其设置。直接从Configure()创建goroutine时要小心:重新配置可能会导致启动越来越多的goroutine实例。通过Runner接口的Start()和Stop()方法启动和停止它们可能会更好。


                                                完整的源代码可以再这里找到:

                                                https://github.com/VadimIpatov/zabbix-httptrace-plugin.



                                                10


                                                一些更复杂的东西


                                                我们学习了如何编写使用Exporter的插件。确实很简单。现在,让我们尝试创建一个使用Collector和Runner的插件。


                                                我们不要只关注理论上,而去寻找一些实际有用的东西。例如,一个插件可测量HTTP请求花费的时间,然后根据收集的数据计算百分比。


                                                首先,我们将安排如何使用net http httptrace软件包(Go 1.7中引入)来收集数据。

                                                  type timeSample struct {
                                                  DnsLookup float64 `json:"dnsLookup"`
                                                  Connect float64 `json:"connect"`
                                                  TlsHandshake float64 `json:"tlsHandshake"`
                                                  FirstResponseByte float64 `json:"firstResponseByte"`
                                                  Rtt float64 `json:"rtt"`
                                                  }
                                                  func (p *Plugin) measureTime(url string) (timeSample, error) {
                                                  var (
                                                  sample timeSample
                                                  start, connect, dns, tlsHandshake time.Time
                                                  )


                                                  req, _ := http.NewRequest("GET", url, nil)


                                                  trace := &httptrace.ClientTrace{
                                                  DNSStart: func(_ httptrace.DNSStartInfo) {
                                                  dns = time.Now()
                                                  },
                                                  DNSDone: func(_ httptrace.DNSDoneInfo) {
                                                  sample.DnsLookup = float64(time.Since(dns) time.Millisecond)
                                                  },


                                                  ConnectStart: func(_, _ string) {
                                                  connect = time.Now()
                                                  },
                                                  ConnectDone: func(net, addr string, err error) {
                                                  if err != nil {
                                                  p.Errf("unable to connect to host %s: %s", addr, err.Error())
                                                  }
                                                  sample.Connect = float64(time.Since(connect) / time.Millisecond)
                                                  },


                                                  GotFirstResponseByte: func() {
                                                  sample.FirstResponseByte = float64(time.Since(start) / time.Millisecond)
                                                  },


                                                  TLSHandshakeStart: func() {
                                                  tlsHandshake = time.Now()
                                                  },
                                                  TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
                                                  sample.TlsHandshake = float64(time.Since(tlsHandshake) / time.Millisecond)
                                                  },
                                                  }
                                                  ctx, cancel := context.WithTimeout(req.Context(), time.Duration(p.options.Timeout)*time.Second)
                                                  defer cancel()
                                                  req = req.WithContext(httptrace.WithClientTrace(ctx, trace))


                                                  start = time.Now()
                                                  if _, err := http.DefaultTransport.RoundTrip(req); err != nil {
                                                  return timeSample{}, err
                                                  }
                                                  sample.Rtt = float64(time.Since(start) / time.Millisecond)


                                                  return sample, nil
                                                  }

                                                  如果要计算百分比,则需要将数据存储在某处。因此,我们将使用循环缓冲区。为了使我们的生活更轻松,我们将采用现成的解决方案,您可以在这里找到它-

                                                  github.com/VadimIpatov/gcircularqueue。这样做可能不是最有效的方法,但是它将保持我们的代码可读性。这次我们可以再次利用开源和丰富的Go生态系统来计算百分比:这里我使用的是github.com/montanaflynn/stats。现在,我们可以定义存储数据的结构。

                                                    type Plugin struct {
                                                    plugin.Base
                                                    urls map[string]*urlUnit
                                                    sync.Mutex
                                                    options Options
                                                    }


                                                    type urlUnit struct {
                                                    url string
                                                    history *gcircularqueue.CircularQueue
                                                    accessed time.Time // last access time
                                                    modified time.Time // data collect time
                                                    }

                                                    为了初始化和清除资源,我们将使用Runner接口中的Start()和Stop()方法。

                                                      func (p *Plugin) Start() {
                                                      p.urls = make(map[string]*urlUnit)
                                                      }


                                                      func (p *Plugin) Stop() {
                                                      p.urls = nil
                                                      }

                                                      我们使用 Collector 来收集数据。

                                                        func (p *Plugin) Collect() (err error) {
                                                        now := time.Now()
                                                        p.Lock()
                                                        for key, url := range p.urls {
                                                        if now.Sub(url.accessed) > maxInactivityPeriod {
                                                        p.Debugf("removed expired url %s", url.url)
                                                        delete(p.urls, key)
                                                        continue
                                                        }
                                                        res, err := p.measureTime(url.url)
                                                        if err != nil {
                                                        p.Errf(err.Error())
                                                        continue
                                                        }
                                                        url.history.Push(res)
                                                        if url.history.IsFull() {
                                                        _ = url.history.Shift()
                                                        }
                                                        url.modified = now
                                                        }
                                                        p.Unlock()


                                                        return
                                                        }


                                                        func (p *Plugin) Period() int {
                                                        return p.options.Interval
                                                        }

                                                        在这里,我们使用一个循环遍历URL列表(需要填充列表),为每个URL调用p.measureTime(url.url)方法,然后将结果存储在缓冲区中。为了将每个URL的数据与请求时间绑定在一起,我们将后者存储在url.modified中。


                                                        我们还会删除一段时间未请求的网址。


                                                        你还记得吗?Collector无法导出数据,因此我们需要使用Exporter。

                                                          func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
                                                          if len(params) != 1 {
                                                          return nil, errors.New("Wrong parameters.")
                                                          }


                                                          url, err := parseURL(params[0])
                                                          if err != nil {
                                                          return nil, err
                                                          }


                                                          switch key {
                                                          case keyHttpTraceStats:
                                                          if _, ok := p.urls[url]; !ok {
                                                          p.urls[url] = &urlUnit{
                                                          url: url,
                                                          history: gcircularqueue.NewCircularQueue(maxHistory),
                                                          }
                                                          }
                                                          p.Lock()
                                                          defer p.Unlock()
                                                          p.urls[url].accessed = time.Now()
                                                          if p.urls[url].history.Len() < minStatRange {
                                                          // no data gathered yet
                                                          return
                                                          }


                                                          data := prepareData(p.urls[url].history.Elements())


                                                          jsonRes, err := json.Marshal(stat{
                                                          // Median: timeSample{...},
                                                          // P75: timeSample{...},
                                                          // P95: timeSample{...},
                                                          P99: timeSample{
                                                          DnsLookup: percentile(data[metricDnsLookup], p99),
                                                          Connect: percentile(data[metricConnect], p99),
                                                          TlsHandshake: percentile(data[metricTlsHandshake], p99),
                                                          FirstResponseByte: percentile(data[metricFirstResponseByte], p99),
                                                          Rtt: percentile(data[metricRtt], p99),
                                                          },
                                                          })
                                                          if err != nil {
                                                          p.Errf(err.Error())
                                                          return nil, errors.New("Cannot marshal JSON.")
                                                          }
                                                          value := string(jsonRes)
                                                          return plugin.Result{
                                                          Value: &value,
                                                          Ts: p.urls[url].modified,
                                                          }, nil


                                                          default:
                                                          return nil, plugin.UnsupportedMetricError
                                                          }
                                                          }

                                                          在这里你可以看到我们在Collect()和Export()中使用了互斥锁,因为它们都必须有权访问共享数据。
                                                          这是我们的插件将返回的内容(输出已格式化以使其更具可读性):

                                                            $ zabbix_get -s zabbix.local -k "httptrace.stats[yoursite.com]"
                                                            {
                                                            "median": {
                                                            "dnsLookup": 13,
                                                            "connect": 28,
                                                            "tlsHandshake": 56,
                                                            "firstResponseByte": 126.5,
                                                            "rtt": 126.5
                                                            },
                                                            "p75": {
                                                            "dnsLookup": 20,
                                                            "connect": 31,
                                                            "tlsHandshake": 60,
                                                            "firstResponseByte": 138.5,
                                                            "rtt": 138.5
                                                            },
                                                            "p95": {
                                                            "dnsLookup": 22.5,
                                                            "connect": 35,
                                                            "tlsHandshake": 78.5,
                                                            "firstResponseByte": 159.5,
                                                            "rtt": 159.5
                                                            },
                                                            "p99": {
                                                            "dnsLookup": 50,
                                                            "connect": 51.5,
                                                            "tlsHandshake": 125.5,
                                                            "firstResponseByte": 266.5,
                                                            "rtt": 266.5
                                                            }
                                                            }

                                                            完整的源代码可以在这里找到:

                                                            https://github.com/VadimIpatov/zabbix-httptrace-plugin.



                                                            11


                                                            如何自监控


                                                            如果需要,agent会提供一个metrics命令,这是一个runtime命令,用于显示每个已创建的插件及其工作负载的状态。


                                                            它使用非常简单:

                                                              $ zabbix_agent2 -R metrics
                                                              ...
                                                              [Weather]
                                                              active: true
                                                              capacity: 0/100
                                                              tasks: 0
                                                              weather.temp: Returns Celsius temperature.
                                                              ...

                                                              您也可以通过HTTP获取此信息。在agent配置文件中设置StatusPort =参数,重新启动agent,然后使用浏览器访问以下地址:

                                                              http:// <ZabbixAgentHost>:<端口> /状态。


                                                              点击查看原文 



                                                              欢迎关注

                                                              Zabbix开源社区

                                                              分享更多精彩内容


                                                              联系我们

                                                              电话:17502189550(微信同号)

                                                              邮箱:china@zabbix.com

                                                              网站:www.zabbix.com/cn  www.grandage.cn

                                                              一键关注

                                                              关注公众号

                                                              加入社区群

                                                              Zabbix社区,因你而更美好


                                                              点击在看,把好文章分享给你的朋友(•̀ᴗ•́)و ̑̑



                                                              文章转载自Zabbix开源社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                              评论