github.com/google/cloudprober@v0.11.3/docs/content/how-to/extensions.md (about)

     1  ---
     2  menu:
     3      main:
     4          parent: "How-Tos"
     5          weight: 40
     6  title: "Extending Cloudprober"
     7  date: 2018-10-29T17:24:32-07:00
     8  ---
     9  Cloudprober allows you to extend it across "probe" and "target" dimensions,
    10  that is, you can add new probe and target types to it without having to fork
    11  the entire codebase. Note that to extend cloudprober in this way, you will have
    12  to maintain your own cloudprober binary (which is mostly a wrapper around the
    13  "cloudprober package"), but you'll be able to use rest of the cloudprober code
    14  from the common location.
    15  
    16  ## Sample probe type
    17  
    18  To demonstrate how it works, let's add a new probe-type to Cloudprober. We'll
    19  take the sample redis probe that we added in the
    20  [external probe how-to]({{< ref "/how-to/external-probe.md#sample-probe" >}}),
    21  and convert it into a probe type that one can easily re-use. Let's say that
    22  this probe-type provides a way to test redis server functionality and it takes
    23  the following options - operation (GET vs SET vs DELETE), key, value. This
    24  probe's configuration looks like this:
    25  
    26  {{< highlight shell >}}
    27  probe {
    28    name: "redis_set"
    29    type: EXTENSION
    30    targets {
    31      host_names: "localhost:6379"
    32    }
    33    redis_probe {
    34      op: "set"
    35      key: "testkey"
    36      value: "testval"
    37    }
    38  }
    39  {{< / highlight >}}
    40  
    41  To make cloudprober understand this config, we'll have to do a few things:
    42  
    43  * Define the probe config in a protobuf (.proto) file and mark it as an
    44    extension of the overall config.
    45  
    46  * Implement the probe type, possibly as a Go package, even though it can
    47    be embedded directly into the top-level binary.
    48  
    49  * Create a new cloudprober binary that includes the new probe type package.
    50  
    51  ## Protobuf for the new probe type
    52  
    53  Let's create a new directory for our code: `$GOPATH/src/myprober`.
    54  
    55  {{< highlight protobuf >}}
    56  // File: $GOPATH/src/myprober/myprobe/myprobe.proto
    57  
    58  syntax = "proto2";
    59  
    60  import "github.com/google/cloudprober/probes/proto/config.proto";
    61  
    62  package myprober;
    63  
    64  message ProbeConf {
    65    // Redis operation
    66    required string op = 1;
    67  
    68    // Key and value for the redis operation
    69    required string key = 2;
    70    optional string value = 3;
    71  }
    72  
    73  extend cloudprober.probes.ProbeDef {
    74    optional ProbeConf redis_probe = 200;
    75  }
    76  {{< / highlight >}}
    77  
    78  Let's generate Go code for this protobuf:
    79  
    80  {{< highlight bash >}}
    81  # From the myprober directory
    82  protoc --go_out=.,import_path=myprobe:. --proto_path=$GOPATH/src:. myprobe/*.proto
    83  
    84  $ ls myprobe/
    85  myprobe.pb.go  myprobe.proto
    86  {{< /highlight >}}
    87  
    88  ## Implement the probe type
    89  
    90  Now let's implement our probe type. Our probe type should implement the
    91  [probes.Probe](https://godoc.org/github.com/google/cloudprober/probes#Probe) interface.
    92  
    93  {{< highlight go >}}
    94  package myprobe
    95  
    96  // Probe holds aggregate information about all probe runs, per-target.
    97  type Probe struct {
    98    name    string
    99    c       *configpb.ProbeConf
   100    targets []string
   101    opts    *options.Options
   102    ...
   103  }
   104  
   105  // Init initializes the probe with the given params.
   106  func (p *Probe) Init(name string, opts *options.Options) error {
   107    c, ok := opts.ProbeConf.(*ProbeConf)
   108    if !ok {
   109      return fmt.Errorf("not a my probe config")
   110    }
   111    // initialize p fields, p.name = name, etc.
   112  }
   113  
   114  // Start runs the probe indefinitely, at the configured interval.
   115  func (p *Probe) Start(ctx context.Context, dataChan chan *metrics.EventMetrics) {
   116    probeTicker := time.NewTicker(p.opts.Interval)
   117  
   118    for {
   119      select {
   120      case <-ctx.Done():
   121        probeTicker.Stop()
   122        return
   123      case <-probeTicker.C:
   124        for _, em := range p.res {
   125          dataChan <- em
   126        }
   127        p.targets = p.opts.Targets.List()
   128        ...
   129        probeCtx, cancelFunc := context.WithDeadline(ctx, time.Now().Add(p.opts.Timeout))
   130        p.runProbe(probeCtx)
   131        cancelFunc()
   132      }
   133    }
   134  }
   135  
   136  // runProbe runs probe for all targets and update EventMetrics.
   137  func (p *Probe) runProbe(ctx context.Context) {
   138    p.targets = p.opts.Targets.List()
   139  
   140    var wg sync.WaitGroup
   141    for _, target := range p.targets {
   142      wg.Add(1)
   143  
   144      go func(target string, em *metrics.EventMetrics) {
   145        defer wg.Done()
   146        em.Metric("total").AddInt64(1)
   147        start := time.Now()
   148        err := p.runProbeForTarget(ctx, target) // run probe just for a single target
   149        if err != nil {
   150          p.l.Errorf(err.Error())
   151          return
   152        }
   153        em.Metric("success").AddInt64(1)
   154        em.Metric("latency").AddFloat64(time.Now().Sub(start).Seconds() / p.opts.LatencyUnit.Seconds())
   155      }(target, p.res[target])
   156  
   157    }
   158  
   159    wg.Wait()
   160  }
   161  {{< / highlight >}}
   162  
   163  Full example in
   164  [examples/extensions/myprober/myprobe/myprobe.go](https://github.com/google/cloudprober/blob/master/examples/extensions/myprober/myprobe/myprobe.go).
   165  
   166  This probe type sets or gets (depending on the configuration) a key-valye in
   167  redis and records success and time taken (latency) if operation is successful.
   168  
   169  ## Implement a cloudprober binary that includes support for our probe
   170  
   171  {{< highlight go >}}
   172  package main
   173  
   174  ...
   175  
   176  func main() {
   177    flag.Parse()
   178  
   179    // Register our probe type
   180    probes.RegisterProbeType(int(myprobe.E_RedisProbe.Field),
   181                             func() probes.Probe { return &myprobe.Probe{} })
   182  
   183    err := cloudprober.InitFromConfig(getConfig()) // getConfig not shown here.
   184    if err != nil {
   185      glog.Exitf("Error initializing cloudprober. Err: %v", err)
   186    }
   187  
   188    // web.Init sets up web UI for cloudprober.
   189    web.Init()
   190  
   191    cloudprober.Start(context.Background())
   192  
   193    // Wait forever
   194    select {}
   195  }
   196  {{< / highlight >}}
   197  
   198  Full example in
   199  [examples/extensions/myprober/myprober.go](https://github.com/google/cloudprober/blob/master/examples/extensions/myprober/myprober.go).
   200  
   201  Let's write a test config that uses the newly defined probe type:
   202  
   203  {{< highlight bash >}}
   204  probe {
   205    name: "redis_set"
   206    type: EXTENSION
   207    interval_msec: 10000
   208    timeout_msec: 5000
   209    targets {
   210      host_names: "localhost:6379"
   211    }
   212    [myprober.redis_probe] {
   213      op: "set"
   214      key: "testkey"
   215      value: "testval"
   216    }
   217  }
   218  {{< / highlight >}}
   219  
   220  Full example in
   221  [examples/extensions/myprober/myprober.cfg](https://github.com/google/cloudprober/blob/master/examples/extensions/myprober/myprober.cfg).
   222  
   223  Let's compile our prober and run it with the above config:
   224  
   225  {{< highlight bash >}}
   226  go build ./myprober.go
   227  ./myprober --config_file=myprober.cfg
   228  {{< / highlight >}}
   229  
   230  you should see an output like the following:
   231  {{< highlight text >}}
   232  cloudprober 1540848577649139842 1540848587 labels=ptype=redis,probe=redis_set,dst=localhost:6379 total=31 success=31 latency=70579.823
   233  cloudprober 1540848577649139843 1540848887 labels=ptype=sysvars,probe=sysvars hostname="manugarg-macbookpro5.roam.corp.google.com" start_timestamp="1540848577"
   234  cloudprober 1540848577649139844 1540848887 labels=ptype=sysvars,probe=sysvars uptime_msec=310007.784 gc_time_msec=0.000 mallocs=14504 frees=826
   235  cloudprober 1540848577649139845 1540848887 labels=ptype=sysvars,probe=sysvars goroutines=12 mem_stats_sys_bytes=7211256
   236  cloudprober 1540848577649139846 1540848587 labels=ptype=redis,probe=redis_set,dst=localhost:6379 total=32 success=32 latency=72587.981
   237  cloudprober 1540848577649139847 1540848897 labels=ptype=sysvars,probe=sysvars hostname="manugarg-macbookpro5.roam.corp.google.com" start_timestamp="1540848577"
   238  cloudprober 1540848577649139848 1540848897 labels=ptype=sysvars,probe=sysvars uptime_msec=320006.541 gc_time_msec=0.000 mallocs=14731 frees=844
   239  cloudprober 1540848577649139849 1540848897 labels=ptype=sysvars,probe=sysvars goroutines=12 mem_stats_sys_bytes=7211256
   240  {{< / highlight >}}
   241  
   242  You can import this data in prometheus following the process outlined at:
   243  [Running Prometheus]({{< ref "/getting-started.md#running-prometheus" >}}).
   244  
   245  ## Conclusion
   246  
   247  The article shows how to add a new probe type to cloudprober. Extending
   248  cloudprober allows you to implement new probe types that may make sense for your
   249  organization, but not for the open source community. You have to implement the
   250  logic for the probe type, but other cloudprober features work as it is --
   251  targets, metrics (e.g. latency distribution if you configure it), surfacers -
   252  data can be multiple systems simultaneously, etc.
   253  
   254