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