telegraf/plugins/inputs/marklogic/marklogic.go

261 lines
8.1 KiB
Go

package marklogic
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/tls"
"github.com/influxdata/telegraf/plugins/inputs"
)
// Marklogic configuration toml
type Marklogic struct {
URL string `toml:"url"`
Hosts []string `toml:"hosts"`
Username string `toml:"username"`
Password string `toml:"password"`
Sources []string
tls.ClientConfig
client *http.Client
}
type MlPointInt struct {
Value int `json:"value"`
}
type MlPointFloat struct {
Value float64 `json:"value"`
}
type MlPointBool struct {
Value bool `json:"value"`
}
// MarkLogic v2 management api endpoints for hosts status
const statsPath = "/manage/v2/hosts/"
const viewFormat = "view=status&format=json"
type MlHost struct {
HostStatus struct {
ID string `json:"id"`
Name string `json:"name"`
StatusProperties struct {
Online MlPointBool `json:"online"`
LoadProperties struct {
TotalLoad MlPointFloat `json:"total-load"`
} `json:"load-properties"`
RateProperties struct {
TotalRate MlPointFloat `json:"total-rate"`
} `json:"rate-properties"`
StatusDetail struct {
Cpus MlPointInt `json:"cpus"`
Cores MlPointInt `json:"cores"`
TotalCPUStatUser float64 `json:"total-cpu-stat-user"`
TotalCPUStatSystem float64 `json:"total-cpu-stat-system"`
TotalCPUStatIdle float64 `json:"total-cpu-stat-idle"`
TotalCPUStatIowait float64 `json:"total-cpu-stat-iowait"`
MemoryProcessSize MlPointInt `json:"memory-process-size"`
MemoryProcessRss MlPointInt `json:"memory-process-rss"`
MemorySystemTotal MlPointInt `json:"memory-system-total"`
MemorySystemFree MlPointInt `json:"memory-system-free"`
MemoryProcessSwapSize MlPointInt `json:"memory-process-swap-size"`
MemorySize MlPointInt `json:"memory-size"`
HostSize MlPointInt `json:"host-size"`
LogDeviceSpace MlPointInt `json:"log-device-space"`
DataDirSpace MlPointInt `json:"data-dir-space"`
QueryReadBytes MlPointInt `json:"query-read-bytes"`
QueryReadLoad MlPointInt `json:"query-read-load"`
MergeReadLoad MlPointInt `json:"merge-read-load"`
MergeWriteLoad MlPointInt `json:"merge-write-load"`
HTTPServerReceiveBytes MlPointInt `json:"http-server-receive-bytes"`
HTTPServerSendBytes MlPointInt `json:"http-server-send-bytes"`
} `json:"status-detail"`
} `json:"status-properties"`
} `json:"host-status"`
}
// Description of plugin returned
func (c *Marklogic) Description() string {
return "Retrieves information on a specific host in a MarkLogic Cluster"
}
var sampleConfig = `
## Base URL of the MarkLogic HTTP Server.
url = "http://localhost:8002"
## List of specific hostnames to retrieve information. At least (1) required.
# hosts = ["hostname1", "hostname2"]
## Using HTTP Basic Authentication. Management API requires 'manage-user' role privileges
# username = "myuser"
# password = "mypassword"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
`
// Init parse all source URLs and place on the Marklogic struct
func (c *Marklogic) Init() error {
if len(c.URL) == 0 {
c.URL = "http://localhost:8002/"
}
for _, u := range c.Hosts {
base, err := url.Parse(c.URL)
if err != nil {
return err
}
base.Path = path.Join(base.Path, statsPath, u)
addr := base.ResolveReference(base)
addr.RawQuery = viewFormat
u := addr.String()
c.Sources = append(c.Sources, u)
}
return nil
}
// SampleConfig to gather stats from localhost, default port.
func (c *Marklogic) SampleConfig() string {
return sampleConfig
}
// Gather metrics from HTTP Server.
func (c *Marklogic) Gather(accumulator telegraf.Accumulator) error {
var wg sync.WaitGroup
if c.client == nil {
client, err := c.createHTTPClient()
if err != nil {
return err
}
c.client = client
}
// Range over all source URL's appended to the struct
for _, serv := range c.Sources {
//fmt.Printf("Encoded URL is %q\n", serv)
wg.Add(1)
go func(serv string) {
defer wg.Done()
if err := c.fetchAndInsertData(accumulator, serv); err != nil {
accumulator.AddError(fmt.Errorf("[host=%s]: %s", serv, err))
}
}(serv)
}
wg.Wait()
return nil
}
func (c *Marklogic) fetchAndInsertData(acc telegraf.Accumulator, url string) error {
ml := &MlHost{}
if err := c.gatherJSONData(url, ml); err != nil {
return err
}
// Build a map of tags
tags := map[string]string{
"source": ml.HostStatus.Name,
"id": ml.HostStatus.ID,
}
// Build a map of field values
fields := map[string]interface{}{
"online": ml.HostStatus.StatusProperties.Online.Value,
"total_load": ml.HostStatus.StatusProperties.LoadProperties.TotalLoad.Value,
"total_rate": ml.HostStatus.StatusProperties.RateProperties.TotalRate.Value,
"ncpus": ml.HostStatus.StatusProperties.StatusDetail.Cpus.Value,
"ncores": ml.HostStatus.StatusProperties.StatusDetail.Cores.Value,
"total_cpu_stat_user": ml.HostStatus.StatusProperties.StatusDetail.TotalCPUStatUser,
"total_cpu_stat_system": ml.HostStatus.StatusProperties.StatusDetail.TotalCPUStatSystem,
"total_cpu_stat_idle": ml.HostStatus.StatusProperties.StatusDetail.TotalCPUStatIdle,
"total_cpu_stat_iowait": ml.HostStatus.StatusProperties.StatusDetail.TotalCPUStatIowait,
"memory_process_size": ml.HostStatus.StatusProperties.StatusDetail.MemoryProcessSize.Value,
"memory_process_rss": ml.HostStatus.StatusProperties.StatusDetail.MemoryProcessRss.Value,
"memory_system_total": ml.HostStatus.StatusProperties.StatusDetail.MemorySystemTotal.Value,
"memory_system_free": ml.HostStatus.StatusProperties.StatusDetail.MemorySystemFree.Value,
"memory_process_swap_size": ml.HostStatus.StatusProperties.StatusDetail.MemoryProcessSwapSize.Value,
"memory_size": ml.HostStatus.StatusProperties.StatusDetail.MemorySize.Value,
"host_size": ml.HostStatus.StatusProperties.StatusDetail.HostSize.Value,
"log_device_space": ml.HostStatus.StatusProperties.StatusDetail.LogDeviceSpace.Value,
"data_dir_space": ml.HostStatus.StatusProperties.StatusDetail.DataDirSpace.Value,
"query_read_bytes": ml.HostStatus.StatusProperties.StatusDetail.QueryReadBytes.Value,
"query_read_load": ml.HostStatus.StatusProperties.StatusDetail.QueryReadLoad.Value,
"merge_read_load": ml.HostStatus.StatusProperties.StatusDetail.MergeReadLoad.Value,
"merge_write_load": ml.HostStatus.StatusProperties.StatusDetail.MergeWriteLoad.Value,
"http_server_receive_bytes": ml.HostStatus.StatusProperties.StatusDetail.HTTPServerReceiveBytes.Value,
"http_server_send_bytes": ml.HostStatus.StatusProperties.StatusDetail.HTTPServerSendBytes.Value,
}
// Accumulate the tags and values
acc.AddFields("marklogic", fields, tags)
return nil
}
func (c *Marklogic) createHTTPClient() (*http.Client, error) {
tlsCfg, err := c.ClientConfig.TLSConfig()
if err != nil {
return nil, err
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsCfg,
},
Timeout: time.Duration(5 * time.Second),
}
return client, nil
}
func (c *Marklogic) gatherJSONData(url string, v interface{}) error {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
if c.Username != "" || c.Password != "" {
req.SetBasicAuth(c.Username, c.Password)
}
response, err := c.client.Do(req)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("marklogic: API responded with status-code %d, expected %d",
response.StatusCode, http.StatusOK)
}
if err = json.NewDecoder(response.Body).Decode(v); err != nil {
return err
}
return nil
}
func init() {
inputs.Add("marklogic", func() telegraf.Input {
return &Marklogic{}
})
}