Fix metric creation when node is offline in jenkins input (#6627)

This commit is contained in:
Daniel Nelson 2019-11-12 11:59:11 -08:00 committed by GitHub
parent ce3ae58ad9
commit bcf1bcf318
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 175 additions and 132 deletions

View File

@ -67,6 +67,7 @@ This plugin does not require a plugin on jenkins and it makes use of Jenkins API
- swap_available - swap_available
- swap_total - swap_total
- response_time - response_time
- num_executors
- jenkins_job - jenkins_job
- tags: - tags:

View File

@ -2,7 +2,6 @@ package jenkins
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
@ -180,27 +179,36 @@ func (j *Jenkins) gatherNodeData(n node, acc telegraf.Accumulator) error {
return nil return nil
} }
tags["arch"] = n.MonitorData.HudsonNodeMonitorsArchitectureMonitor monitorData := n.MonitorData
if monitorData.HudsonNodeMonitorsArchitectureMonitor != "" {
tags["arch"] = monitorData.HudsonNodeMonitorsArchitectureMonitor
}
tags["status"] = "online" tags["status"] = "online"
if n.Offline { if n.Offline {
tags["status"] = "offline" tags["status"] = "offline"
} }
monitorData := n.MonitorData
if monitorData.HudsonNodeMonitorsArchitectureMonitor == "" {
return errors.New("empty monitor data, please check your permission")
}
tags["disk_path"] = monitorData.HudsonNodeMonitorsDiskSpaceMonitor.Path
tags["temp_path"] = monitorData.HudsonNodeMonitorsTemporarySpaceMonitor.Path
fields := map[string]interface{}{ fields := make(map[string]interface{})
"response_time": monitorData.HudsonNodeMonitorsResponseTimeMonitor.Average, fields["num_executors"] = n.NumExecutors
"disk_available": monitorData.HudsonNodeMonitorsDiskSpaceMonitor.Size,
"temp_available": monitorData.HudsonNodeMonitorsTemporarySpaceMonitor.Size, if monitorData.HudsonNodeMonitorsResponseTimeMonitor != nil {
"swap_available": monitorData.HudsonNodeMonitorsSwapSpaceMonitor.SwapAvailable, fields["response_time"] = monitorData.HudsonNodeMonitorsResponseTimeMonitor.Average
"memory_available": monitorData.HudsonNodeMonitorsSwapSpaceMonitor.MemoryAvailable, }
"swap_total": monitorData.HudsonNodeMonitorsSwapSpaceMonitor.SwapTotal, if monitorData.HudsonNodeMonitorsDiskSpaceMonitor != nil {
"memory_total": monitorData.HudsonNodeMonitorsSwapSpaceMonitor.MemoryTotal, tags["disk_path"] = monitorData.HudsonNodeMonitorsDiskSpaceMonitor.Path
fields["disk_available"] = monitorData.HudsonNodeMonitorsDiskSpaceMonitor.Size
}
if monitorData.HudsonNodeMonitorsTemporarySpaceMonitor != nil {
tags["temp_path"] = monitorData.HudsonNodeMonitorsTemporarySpaceMonitor.Path
fields["temp_available"] = monitorData.HudsonNodeMonitorsTemporarySpaceMonitor.Size
}
if monitorData.HudsonNodeMonitorsSwapSpaceMonitor != nil {
fields["swap_available"] = monitorData.HudsonNodeMonitorsSwapSpaceMonitor.SwapAvailable
fields["memory_available"] = monitorData.HudsonNodeMonitorsSwapSpaceMonitor.MemoryAvailable
fields["swap_total"] = monitorData.HudsonNodeMonitorsSwapSpaceMonitor.SwapTotal
fields["memory_total"] = monitorData.HudsonNodeMonitorsSwapSpaceMonitor.MemoryTotal
} }
acc.AddFields(measurementNode, fields, tags) acc.AddFields(measurementNode, fields, tags)
@ -327,24 +335,18 @@ type nodeResponse struct {
} }
type node struct { type node struct {
DisplayName string `json:"displayName"` DisplayName string `json:"displayName"`
Offline bool `json:"offline"` Offline bool `json:"offline"`
MonitorData monitorData `json:"monitorData"` NumExecutors int `json:"numExecutors"`
MonitorData monitorData `json:"monitorData"`
} }
type monitorData struct { type monitorData struct {
HudsonNodeMonitorsArchitectureMonitor string `json:"hudson.node_monitors.ArchitectureMonitor"` HudsonNodeMonitorsArchitectureMonitor string `json:"hudson.node_monitors.ArchitectureMonitor"`
HudsonNodeMonitorsDiskSpaceMonitor nodeSpaceMonitor `json:"hudson.node_monitors.DiskSpaceMonitor"` HudsonNodeMonitorsDiskSpaceMonitor *nodeSpaceMonitor `json:"hudson.node_monitors.DiskSpaceMonitor"`
HudsonNodeMonitorsResponseTimeMonitor struct { HudsonNodeMonitorsResponseTimeMonitor *responseTimeMonitor `json:"hudson.node_monitors.ResponseTimeMonitor"`
Average int64 `json:"average"` HudsonNodeMonitorsSwapSpaceMonitor *swapSpaceMonitor `json:"hudson.node_monitors.SwapSpaceMonitor"`
} `json:"hudson.node_monitors.ResponseTimeMonitor"` HudsonNodeMonitorsTemporarySpaceMonitor *nodeSpaceMonitor `json:"hudson.node_monitors.TemporarySpaceMonitor"`
HudsonNodeMonitorsSwapSpaceMonitor struct {
SwapAvailable float64 `json:"availableSwapSpace"`
SwapTotal float64 `json:"totalSwapSpace"`
MemoryAvailable float64 `json:"availablePhysicalMemory"`
MemoryTotal float64 `json:"totalPhysicalMemory"`
} `json:"hudson.node_monitors.SwapSpaceMonitor"`
HudsonNodeMonitorsTemporarySpaceMonitor nodeSpaceMonitor `json:"hudson.node_monitors.TemporarySpaceMonitor"`
} }
type nodeSpaceMonitor struct { type nodeSpaceMonitor struct {
@ -352,6 +354,17 @@ type nodeSpaceMonitor struct {
Size float64 `json:"size"` Size float64 `json:"size"`
} }
type responseTimeMonitor struct {
Average int64 `json:"average"`
}
type swapSpaceMonitor struct {
SwapAvailable float64 `json:"availableSwapSpace"`
SwapTotal float64 `json:"totalSwapSpace"`
MemoryAvailable float64 `json:"availablePhysicalMemory"`
MemoryTotal float64 `json:"totalPhysicalMemory"`
}
type jobResponse struct { type jobResponse struct {
LastBuild jobBuild `json:"lastBuild"` LastBuild jobBuild `json:"lastBuild"`
Jobs []innerJob `json:"jobs"` Jobs []innerJob `json:"jobs"`

View File

@ -107,7 +107,7 @@ func TestGatherNodeData(t *testing.T) {
wantErr: true, wantErr: true,
}, },
{ {
name: "bad empty monitor data", name: "empty monitor data",
input: mockHandler{ input: mockHandler{
responseMap: map[string]interface{}{ responseMap: map[string]interface{}{
"/api/json": struct{}{}, "/api/json": struct{}{},
@ -119,7 +119,9 @@ func TestGatherNodeData(t *testing.T) {
}, },
}, },
}, },
wantErr: true, output: &testutil.Accumulator{
Metrics: []*testutil.Metric{},
},
}, },
{ {
name: "filtered nodes", name: "filtered nodes",
@ -135,7 +137,6 @@ func TestGatherNodeData(t *testing.T) {
}, },
}, },
}, },
{ {
name: "normal data collection", name: "normal data collection",
input: mockHandler{ input: mockHandler{
@ -147,25 +148,18 @@ func TestGatherNodeData(t *testing.T) {
DisplayName: "master", DisplayName: "master",
MonitorData: monitorData{ MonitorData: monitorData{
HudsonNodeMonitorsArchitectureMonitor: "linux", HudsonNodeMonitorsArchitectureMonitor: "linux",
HudsonNodeMonitorsResponseTimeMonitor: struct { HudsonNodeMonitorsResponseTimeMonitor: &responseTimeMonitor{
Average int64 `json:"average"`
}{
Average: 10032, Average: 10032,
}, },
HudsonNodeMonitorsDiskSpaceMonitor: nodeSpaceMonitor{ HudsonNodeMonitorsDiskSpaceMonitor: &nodeSpaceMonitor{
Path: "/path/1", Path: "/path/1",
Size: 123, Size: 123,
}, },
HudsonNodeMonitorsTemporarySpaceMonitor: nodeSpaceMonitor{ HudsonNodeMonitorsTemporarySpaceMonitor: &nodeSpaceMonitor{
Path: "/path/2", Path: "/path/2",
Size: 245, Size: 245,
}, },
HudsonNodeMonitorsSwapSpaceMonitor: struct { HudsonNodeMonitorsSwapSpaceMonitor: &swapSpaceMonitor{
SwapAvailable float64 `json:"availableSwapSpace"`
SwapTotal float64 `json:"totalSwapSpace"`
MemoryAvailable float64 `json:"availablePhysicalMemory"`
MemoryTotal float64 `json:"totalPhysicalMemory"`
}{
SwapAvailable: 212, SwapAvailable: 212,
SwapTotal: 500, SwapTotal: 500,
MemoryAvailable: 101, MemoryAvailable: 101,
@ -201,42 +195,75 @@ func TestGatherNodeData(t *testing.T) {
}, },
}, },
}, },
{
name: "slave is offline",
input: mockHandler{
responseMap: map[string]interface{}{
"/api/json": struct{}{},
"/computer/api/json": nodeResponse{
Computers: []node{
{
DisplayName: "slave",
MonitorData: monitorData{},
NumExecutors: 1,
Offline: true,
},
},
},
},
},
output: &testutil.Accumulator{
Metrics: []*testutil.Metric{
{
Tags: map[string]string{
"node_name": "slave",
"status": "offline",
},
Fields: map[string]interface{}{
"num_executors": 1,
},
},
},
},
},
} }
for _, test := range tests { for _, test := range tests {
ts := httptest.NewServer(test.input) t.Run(test.name, func(t *testing.T) {
defer ts.Close() ts := httptest.NewServer(test.input)
j := &Jenkins{ defer ts.Close()
Log: testutil.Logger{}, j := &Jenkins{
URL: ts.URL, Log: testutil.Logger{},
ResponseTimeout: internal.Duration{Duration: time.Microsecond}, URL: ts.URL,
NodeExclude: []string{"ignore-1", "ignore-2"}, ResponseTimeout: internal.Duration{Duration: time.Microsecond},
} NodeExclude: []string{"ignore-1", "ignore-2"},
te := j.initialize(&http.Client{Transport: &http.Transport{}}) }
acc := new(testutil.Accumulator) te := j.initialize(&http.Client{Transport: &http.Transport{}})
j.gatherNodesData(acc) acc := new(testutil.Accumulator)
if err := acc.FirstError(); err != nil { j.gatherNodesData(acc)
te = err if err := acc.FirstError(); err != nil {
} te = err
}
if !test.wantErr && te != nil { if !test.wantErr && te != nil {
t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error()) t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error())
} else if test.wantErr && te == nil { } else if test.wantErr && te == nil {
t.Fatalf("%s: expected err, got nil", test.name) t.Fatalf("%s: expected err, got nil", test.name)
} }
if test.output == nil && len(acc.Metrics) > 0 { if test.output == nil && len(acc.Metrics) > 0 {
t.Fatalf("%s: collected extra data", test.name) t.Fatalf("%s: collected extra data", test.name)
} else if test.output != nil && len(test.output.Metrics) > 0 { } else if test.output != nil && len(test.output.Metrics) > 0 {
for k, m := range test.output.Metrics[0].Tags { for k, m := range test.output.Metrics[0].Tags {
if acc.Metrics[0].Tags[k] != m { if acc.Metrics[0].Tags[k] != m {
t.Fatalf("%s: tag %s metrics unmatch Expected %s, got %s\n", test.name, k, m, acc.Metrics[0].Tags[k]) t.Fatalf("%s: tag %s metrics unmatch Expected %s, got %s\n", test.name, k, m, acc.Metrics[0].Tags[k])
}
}
for k, m := range test.output.Metrics[0].Fields {
if acc.Metrics[0].Fields[k] != m {
t.Fatalf("%s: field %s metrics unmatch Expected %v(%T), got %v(%T)\n", test.name, k, m, m, acc.Metrics[0].Fields[k], acc.Metrics[0].Fields[k])
}
} }
} }
for k, m := range test.output.Metrics[0].Fields { })
if acc.Metrics[0].Fields[k] != m {
t.Fatalf("%s: field %s metrics unmatch Expected %v(%T), got %v(%T)\n", test.name, k, m, m, acc.Metrics[0].Fields[k], acc.Metrics[0].Fields[k])
}
}
}
} }
} }
@ -290,21 +317,22 @@ func TestInitialize(t *testing.T) {
}, },
} }
for _, test := range tests { for _, test := range tests {
te := test.input.initialize(mockClient) t.Run(test.name, func(t *testing.T) {
if !test.wantErr && te != nil { te := test.input.initialize(mockClient)
t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error()) if !test.wantErr && te != nil {
} else if test.wantErr && te == nil { t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error())
t.Fatalf("%s: expected err, got nil", test.name) } else if test.wantErr && te == nil {
} t.Fatalf("%s: expected err, got nil", test.name)
if test.output != nil {
if test.input.client == nil {
t.Fatalf("%s: failed %s, jenkins instance shouldn't be nil", test.name, te.Error())
} }
if test.input.MaxConnections != test.output.MaxConnections { if test.output != nil {
t.Fatalf("%s: different MaxConnections Expected %d, got %d\n", test.name, test.output.MaxConnections, test.input.MaxConnections) if test.input.client == nil {
t.Fatalf("%s: failed %s, jenkins instance shouldn't be nil", test.name, te.Error())
}
if test.input.MaxConnections != test.output.MaxConnections {
t.Fatalf("%s: different MaxConnections Expected %d, got %d\n", test.name, test.output.MaxConnections, test.input.MaxConnections)
}
} }
} })
} }
} }
@ -572,50 +600,51 @@ func TestGatherJobs(t *testing.T) {
}, },
} }
for _, test := range tests { for _, test := range tests {
ts := httptest.NewServer(test.input) t.Run(test.name, func(t *testing.T) {
defer ts.Close() ts := httptest.NewServer(test.input)
j := &Jenkins{ defer ts.Close()
Log: testutil.Logger{}, j := &Jenkins{
URL: ts.URL, Log: testutil.Logger{},
MaxBuildAge: internal.Duration{Duration: time.Hour}, URL: ts.URL,
ResponseTimeout: internal.Duration{Duration: time.Microsecond}, MaxBuildAge: internal.Duration{Duration: time.Hour},
JobExclude: []string{ ResponseTimeout: internal.Duration{Duration: time.Microsecond},
"ignore-1", JobExclude: []string{
"apps/ignore-all/*", "ignore-1",
"apps/k8s-cloud/PR-ignore2", "apps/ignore-all/*",
}, "apps/k8s-cloud/PR-ignore2",
} },
te := j.initialize(&http.Client{Transport: &http.Transport{}}) }
acc := new(testutil.Accumulator) te := j.initialize(&http.Client{Transport: &http.Transport{}})
acc.SetDebug(true) acc := new(testutil.Accumulator)
j.gatherJobs(acc) j.gatherJobs(acc)
if err := acc.FirstError(); err != nil { if err := acc.FirstError(); err != nil {
te = err te = err
} }
if !test.wantErr && te != nil { if !test.wantErr && te != nil {
t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error()) t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error())
} else if test.wantErr && te == nil { } else if test.wantErr && te == nil {
t.Fatalf("%s: expected err, got nil", test.name) t.Fatalf("%s: expected err, got nil", test.name)
}
if test.output != nil && len(test.output.Metrics) > 0 {
// sort metrics
sort.Slice(acc.Metrics, func(i, j int) bool {
return strings.Compare(acc.Metrics[i].Tags["name"], acc.Metrics[j].Tags["name"]) < 0
})
for i := range test.output.Metrics {
for k, m := range test.output.Metrics[i].Tags {
if acc.Metrics[i].Tags[k] != m {
t.Fatalf("%s: tag %s metrics unmatch Expected %s, got %s\n", test.name, k, m, acc.Metrics[i].Tags[k])
}
}
for k, m := range test.output.Metrics[i].Fields {
if acc.Metrics[i].Fields[k] != m {
t.Fatalf("%s: field %s metrics unmatch Expected %v(%T), got %v(%T)\n", test.name, k, m, m, acc.Metrics[i].Fields[k], acc.Metrics[0].Fields[k])
}
}
} }
} if test.output != nil && len(test.output.Metrics) > 0 {
// sort metrics
sort.Slice(acc.Metrics, func(i, j int) bool {
return strings.Compare(acc.Metrics[i].Tags["name"], acc.Metrics[j].Tags["name"]) < 0
})
for i := range test.output.Metrics {
for k, m := range test.output.Metrics[i].Tags {
if acc.Metrics[i].Tags[k] != m {
t.Fatalf("%s: tag %s metrics unmatch Expected %s, got %s\n", test.name, k, m, acc.Metrics[i].Tags[k])
}
}
for k, m := range test.output.Metrics[i].Fields {
if acc.Metrics[i].Fields[k] != m {
t.Fatalf("%s: field %s metrics unmatch Expected %v(%T), got %v(%T)\n", test.name, k, m, m, acc.Metrics[i].Fields[k], acc.Metrics[0].Fields[k])
}
}
}
}
})
} }
} }