Skip to content

Commit

Permalink
Heartbeat monitor code generator (elastic#5792)
Browse files Browse the repository at this point in the history
- add support for `make create-monitor`
- code that needs to be adapted by user is marked with `IMPLEMENT_ME`
  • Loading branch information
Steffen Siering authored and ruflin committed Dec 6, 2017
1 parent 978b293 commit 192b565
Show file tree
Hide file tree
Showing 10 changed files with 597 additions and 0 deletions.
5 changes: 5 additions & 0 deletions heartbeat/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ fields:
imports: python-env
@mkdir -p include
@${PYTHON_ENV}/bin/python ${ES_BEATS}/script/generate_imports.py --out monitors/defaults/default.go ${BEAT_PATH}

# Create a new monitor. Requires the parameter MONITOR
.PHONY: create-monitor
create-monitor:
go run scripts/generate_monitor/main.go -monitor=${MONITOR}
184 changes: 184 additions & 0 deletions heartbeat/scripts/generate_monitor/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package main

import (
"bytes"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"text/template"
)

func main() {
var (
monitor string
generatorHome string
path string
)
flag.StringVar(&monitor, "monitor", "", "Monitor name")
flag.StringVar(&generatorHome, "home", "./scripts/generator/{{monitor}}", "Generator home path")
flag.StringVar(&path, "path", "./monitors/active", "monitor output directory")
flag.Parse()

if monitor == "" {
if err := prompt("Monitor name [example]: ", &monitor); err != nil {
fatal(err)
}

if monitor == "" {
monitor = "example"
}
}

env := map[string]interface{}{
// variables
"monitor": noDot(monitor),

// functions
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
}
if err := generate(generatorHome, filepath.Join(path, monitor), env); err != nil {
fatal(err)
}
}

func prompt(msg string, to interface{}) error {
fmt.Print(msg)
_, err := fmt.Scanln(to)
return err
}

// create a template function, such that the variable can be accessed without
// the leading `.`.
func noDot(v string) interface{} {
return func() string { return v }
}

func fatal(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

func generate(generatorPath string, outPath string, env map[string]interface{}) error {
root, err := filepath.Abs(generatorPath)
if err != nil {
return err
}

outPath, err = filepath.Abs(outPath)
if err != nil {
return err
}

if err := os.MkdirAll(outPath, os.ModeDir|os.ModePerm); err != nil {
return err
}

const tmplExt = ".tmpl"

return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == root {
return nil
}

name, err := execNameTemplate(path[len(root)+1:], env)
if err != nil {
return err
}

if info.IsDir() {
dir := filepath.Join(outPath, name)
return os.MkdirAll(dir, os.ModeDir|os.ModePerm)
}

if filepath.Ext(name) != tmplExt {
return copyFile(filepath.Join(outPath, name), path)
}

// template file.
name = name[:len(name)-len(tmplExt)]
return copyTemplate(filepath.Join(outPath, name), path, env)
})
}

func execNameTemplate(in string, env map[string]interface{}) (string, error) {
var err error
tmpl := withFunc(template.New("name"), env)
tmpl, err = tmpl.Parse(in)
if err != nil {
return "", err
}

var buf bytes.Buffer
err = tmpl.Execute(&buf, withEnv(env))
return buf.String(), err
}

func copyTemplate(to, tmplPath string, env map[string]interface{}) error {
var err error
tmpl := withFunc(template.New(filepath.Base(tmplPath)), env)
tmpl, err = tmpl.ParseFiles(tmplPath)
if err != nil {
return err
}

out, err := os.Create(to)
if err != nil {
return err
}
defer out.Close()

tmpl = tmpl.Funcs(env)
return tmpl.Execute(out, env)
}

func copyFile(to, from string) error {
in, err := os.Open(from)
if err != nil {
return err
}
defer in.Close()

out, err := os.Create(to)
if err != nil {
return err
}
defer out.Close()

_, err = io.Copy(out, in)
return err
}

func withFunc(tmpl *template.Template, env map[string]interface{}) *template.Template {
return tmpl.Funcs(filterEnv(func(v interface{}) bool {
return reflect.TypeOf(v).Kind() == reflect.Func
}, env))
}

func withEnv(env map[string]interface{}) map[string]interface{} {
return filterEnv(func(v interface{}) bool {
return reflect.TypeOf(v).Kind() != reflect.Func
}, env)
}

func filterEnv(pred func(interface{}) bool, env map[string]interface{}) map[string]interface{} {
tmp := map[string]interface{}{}
for k, v := range env {
if pred(v) {
tmp[k] = v
}
}

if len(tmp) == 0 {
return nil
}
return tmp
}
33 changes: 33 additions & 0 deletions heartbeat/scripts/generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Readme

Code generator for Heartbeat monitors.

In order to add a new monitor type to Heartbeat, run `make create-monitor
MONITOR=<name>` from the Heartbeat directory.

```
$ cd $GOPATH/src/github.com/elastic/beats/heartbeat
$ make create-monitor MONITOR=<name>
$ make update
$ make # build heartbeat
```

`make update` is required to update the import list, such that the new monitor
is compiled into Heartbeat.

The new monitor will be added to the `monitors/active/<name>` sub directory.

Monitor structure:
- `config.go`: The monitor configuration options and configuration validation.
- `check.go`: The monitor validation support.
- `job.go`: Implements the ping function for connecting and validating an
endpoint. This file generates the monitor specific fields to an event.
- `<name>.go`: The monitor entrypoint registering the factory for setting up
monitoring jobs.
- `_meta/fields.yml`: Document the monitors event field.
- `_meta/config.yml`: Minimal sample configuration file
- `_meta/config.reference.yml`: Reference configuration file. All availalbe
settings are documented in the reference file.

Code comments tagged with `IMPLEMENT_ME` in the go and meta files give details
on changes required to implement a new monitor type.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
- type: {{monitor}}

# Monitor name used for job name and document type
#name: {{monitor}}

# Enable/Disable monitor
#enabled: true

# Configure task schedule
schedule: '@every 30s'

# list of hosts to monitor
hosts: ["localhost"]

# list of ports to ping (used if host is given without portname)
ports: [12345]

# SOCKS5 proxy url
#proxy_url: ''

# Resolve hostnames locally instead on SOCKS5 server:
#proxy_use_local_resolver: false

# Configure file json file to be watched for changes to the monitor:
#watch.poll_file:
# Path to check for updates.
#path:

# Interval between file file changed checks.
#interval: 5s


# Configure IP protocol types to ping on if hostnames are configured. Ping
# all resolvable IPs if `mode` is `all`, or only one IP if `mode` is `any`.
ipv4: true
ipv6: true
mode: any

# Configure file json file to be watched for changes to the monitor:
#watch.poll_file:
# Path to check for updates.
#path:

# Interval between file file changed checks.
#interval: 5s

# Total test connection and data exchange timeout
#timeout: 16s


# TLS/SSL connection settings:
#ssl:
# Certificate Authorities
#certificate_authorities: ['']

# Required TLS protocols
#supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"]

# IMPLEMENT_ME: document check/validation settings
#check:
#request:
#response:
9 changes: 9 additions & 0 deletions heartbeat/scripts/generator/{{monitor}}/_meta/config.yml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- type: {{monitor}}
# list of hosts to monitor
hosts: ["localhost:12345"]

# Configure task schedule
schedule: '@every 30s'

# Total test connection and data exchange timeout
#timeout: 16s
22 changes: 22 additions & 0 deletions heartbeat/scripts/generator/{{monitor}}/_meta/fields.yml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
- key: {{monitor}}
title: "{{monitor|upper}} monitor"
description:
fields:
- name: {{monitor}}
type: group
description: >
{{monitor|upper}} related fields.
fields:
- name: rtt
type: group
description: >
{{monitor|upper}} layer round trip times.
fields:
- name: validate
type: group
description: >
Duration of validation step based on existing {{monitor|upper}} connection.
fields:
- name: us
type: long
description: Duration in microseconds
15 changes: 15 additions & 0 deletions heartbeat/scripts/generator/{{monitor}}/check.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package {{monitor}}

import "net"

// IMPLEMENT_ME: implement validator
type validator struct {
}

func makeValidator(config *config) (*validator, error) {
return &validator{}, nil
}

func (v *validator) Check(conn net.Conn) error {
return nil
}
Loading

0 comments on commit 192b565

Please sign in to comment.