blob: 694e345d5b8f2679f11f162461eb9dcd6f9e3ba9 [file] [log] [blame]
package swarming_metrics
import (
"strconv"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
swarming_api "go.chromium.org/luci/common/api/swarming/swarming/v1"
"go.skia.org/infra/go/metrics2"
metrics_util "go.skia.org/infra/go/metrics2/testutils"
"go.skia.org/infra/go/swarming"
"go.skia.org/infra/go/testutils/unittest"
)
const (
MOCK_POOL = "SomePool"
MOCK_SERVER = "SomeServer"
)
// getPromClient creates a fresh Prometheus Registry and
// a fresh Prometheus Client. This wipes out all previous metrics.
func getPromClient() metrics2.Client {
prometheus.DefaultRegisterer = prometheus.NewRegistry()
return metrics2.NewPromClient()
}
func TestDeadQuarantinedBotMetrics(t *testing.T) {
unittest.SmallTest(t)
ms := swarming.NewMockApiClient()
defer ms.AssertExpectations(t)
now := time.Date(2017, 9, 1, 12, 0, 0, 0, time.UTC)
type expectations struct {
botID string
quarantined bool
isDead bool
lastSeenDelta time.Duration
dimensions map[string][]string
}
ex := []expectations{
{
botID: "bot-a",
quarantined: false,
isDead: true,
lastSeenDelta: 18 * time.Minute,
dimensions: map[string][]string{
swarming.DIMENSION_OS_KEY: {"Android"},
swarming.DIMENSION_DEVICE_TYPE_KEY: {"Nexus5x"},
swarming.DIMENSION_DEVICE_OS_KEY: {"P", "PPR1.180610.009"},
swarming.DIMENSION_QUARANTINED_KEY: {"Device Missing"},
},
},
{
botID: "bot-b",
quarantined: true,
isDead: false,
lastSeenDelta: 3 * time.Minute,
dimensions: map[string][]string{
swarming.DIMENSION_OS_KEY: {"Linux", "Debian-9.8"},
},
},
{
botID: "bot-c",
quarantined: false,
isDead: false,
lastSeenDelta: 1 * time.Minute,
dimensions: map[string][]string{
swarming.DIMENSION_OS_KEY: {"Windows", "Windows-10"},
},
},
}
b := []*swarming_api.SwarmingRpcsBotInfo{}
for _, e := range ex {
dims := make([]*swarming_api.SwarmingRpcsStringListPair, 0, len(e.dimensions))
for k, v := range e.dimensions {
dims = append(dims, &swarming_api.SwarmingRpcsStringListPair{
Key: k,
Value: v,
})
}
b = append(b, &swarming_api.SwarmingRpcsBotInfo{
BotId: e.botID,
LastSeenTs: now.Add(-e.lastSeenDelta).Format("2006-01-02T15:04:05"),
IsDead: e.isDead,
Quarantined: e.quarantined,
FirstSeenTs: now.Add(-24 * time.Hour).Format("2006-01-02T15:04:05"),
Dimensions: dims,
})
}
ms.On("ListBotsForPool", MOCK_POOL).Return(b, nil)
ms.On("ListBotTasks", mock.AnythingOfType("string"), 1).Return([]*swarming_api.SwarmingRpcsTaskResult{}, nil)
pc := getPromClient()
newMetrics, err := reportBotMetrics(now, ms, pc, MOCK_POOL, MOCK_SERVER)
require.NoError(t, err)
require.Len(t, newMetrics, 21, "3 bots * 7 metrics each = 21 expected metrics")
for _, e := range ex {
tags := map[string]string{
"bot": e.botID,
"pool": MOCK_POOL,
"swarming": MOCK_SERVER,
}
for _, d := range []string{
swarming.DIMENSION_OS_KEY,
swarming.DIMENSION_DEVICE_TYPE_KEY,
swarming.DIMENSION_DEVICE_OS_KEY,
swarming.DIMENSION_QUARANTINED_KEY,
} {
tags[d] = ""
if len(e.dimensions[d]) > 0 {
tags[d] = e.dimensions[d][len(e.dimensions[d])-1]
}
}
// even though this is a (really big) int, JSON notation returns scientific notation
// for large enough ints, which means we need to ParseFloat, the only parser we have
// that can read Scientific notation.
actual, err := strconv.ParseFloat(metrics_util.GetRecordedMetric(t, MEASUREMENT_SWARM_BOTS_LAST_SEEN, tags), 64)
require.NoError(t, err)
require.Equalf(t, int64(e.lastSeenDelta), int64(actual), "Wrong last seen time for metric %s", MEASUREMENT_SWARM_BOTS_LAST_SEEN)
toCheck := []string{"too_hot", "low_battery", "available", "<none>"}
for _, extraTag := range toCheck {
tags["device_state"] = extraTag
actual, err = strconv.ParseFloat(metrics_util.GetRecordedMetric(t, "swarming_bots_quarantined", tags), 64)
require.NoError(t, err)
expected := 0
if e.quarantined && extraTag == "<none>" {
expected = 1
}
require.Equalf(t, int64(expected), int64(actual), "Wrong is quarantined for metric %s + tag %s", MEASUREMENT_SWARM_BOTS_QUARANTINED, extraTag)
}
}
}
func TestLastTaskBotMetrics(t *testing.T) {
unittest.SmallTest(t)
ms := swarming.NewMockApiClient()
defer ms.AssertExpectations(t)
now := time.Date(2017, 9, 1, 12, 0, 0, 0, time.UTC)
ms.On("ListBotsForPool", MOCK_POOL).Return([]*swarming_api.SwarmingRpcsBotInfo{
{
BotId: "my-bot",
LastSeenTs: now.Add(-time.Minute).Format("2006-01-02T15:04:05"),
IsDead: false,
Quarantined: false,
Dimensions: []*swarming_api.SwarmingRpcsStringListPair{
{
Key: swarming.DIMENSION_OS_KEY,
Value: []string{"Android"},
},
{
Key: swarming.DIMENSION_DEVICE_TYPE_KEY,
Value: []string{"Nexus5x"},
},
{
Key: swarming.DIMENSION_DEVICE_OS_KEY,
Value: []string{"P", "PPR1.180610.009"},
},
{
Key: swarming.DIMENSION_QUARANTINED_KEY,
Value: []string{"Device Missing"},
},
},
},
}, nil)
ms.On("ListBotTasks", "my-bot", 1).Return([]*swarming_api.SwarmingRpcsTaskResult{
{
ModifiedTs: now.Add(-31 * time.Minute).Format("2006-01-02T15:04:05"),
},
}, nil)
pc := getPromClient()
newMetrics, err := reportBotMetrics(now, ms, pc, MOCK_POOL, MOCK_SERVER)
require.NoError(t, err)
require.Len(t, newMetrics, 7, "1 bot * 7 metrics = 7 expected metrics")
tags := map[string]string{
"bot": "my-bot",
"pool": MOCK_POOL,
"swarming": MOCK_SERVER,
swarming.DIMENSION_OS_KEY: "Android",
swarming.DIMENSION_DEVICE_TYPE_KEY: "Nexus5x",
swarming.DIMENSION_DEVICE_OS_KEY: "PPR1.180610.009",
swarming.DIMENSION_QUARANTINED_KEY: "Device Missing",
}
// even though this is a (really big) int, JSON notation returns scientific notation
// for large enough ints, which means we need to ParseFloat, the only parser we have
// that can read Scientific notation.
actual, err := strconv.ParseFloat(metrics_util.GetRecordedMetric(t, MEASUREMENT_SWARM_BOTS_LAST_TASK, tags), 64)
require.NoError(t, err)
require.Equalf(t, int64(31*time.Minute), int64(actual), "Wrong last seen time for metric %s", MEASUREMENT_SWARM_BOTS_LAST_TASK)
}
func TestBotTemperatureMetrics(t *testing.T) {
unittest.SmallTest(t)
ms := swarming.NewMockApiClient()
defer ms.AssertExpectations(t)
now := time.Date(2017, 9, 1, 12, 0, 0, 0, time.UTC)
ms.On("ListBotsForPool", MOCK_POOL).Return([]*swarming_api.SwarmingRpcsBotInfo{
{
BotId: "my-bot-no-temp",
LastSeenTs: now.Add(-3 * time.Minute).Format("2006-01-02T15:04:05"),
State: `{}`,
},
{
BotId: "my-bot-no-device",
LastSeenTs: now.Add(-2 * time.Minute).Format("2006-01-02T15:04:05"),
State: `{"temp": {"thermal_zone0": 27.8,"thermal_zone1": 29.8,"thermal_zone2": 36}}`,
},
{
BotId: "my-bot-device",
LastSeenTs: now.Add(-time.Minute).Format("2006-01-02T15:04:05"),
State: `{
"temp": {"thermal_zone0": 42.5000000000000000000000000000001},
"devices": {
"abcdefg": {
"battery": {
"power": ["USB"],
"temperature": 248
},
"temp": {
"merble": 2878.9,
"gerble": 40.03,
"battery": 26,
"tsens_tz_sensor1": 37,
"tsens_tz_sensor2": 412,
"max77621-gpu": 100,
"dram": 2
},
"state": "too_hot"
}
}
}`,
},
}, nil)
ms.On("ListBotTasks", mock.AnythingOfType("string"), 1).Return([]*swarming_api.SwarmingRpcsTaskResult{
{
ModifiedTs: now.Add(-31 * time.Minute).Format("2006-01-02T15:04:05"),
},
}, nil)
pc := getPromClient()
newMetrics, err := reportBotMetrics(now, ms, pc, MOCK_POOL, MOCK_SERVER)
require.NoError(t, err)
require.Len(t, newMetrics, 34, "24 bot metrics + 10 temp metrics = 31 expected metrics")
expected := map[string]float64{
"thermal_zone0": 28.0,
"thermal_zone1": 30.0,
"thermal_zone2": 36.0,
}
for z, v := range expected {
tags := map[string]string{
"bot": "my-bot-no-device",
"pool": MOCK_POOL,
"swarming": MOCK_SERVER,
"temp_zone": z,
swarming.DIMENSION_OS_KEY: "",
swarming.DIMENSION_DEVICE_TYPE_KEY: "",
swarming.DIMENSION_DEVICE_OS_KEY: "",
swarming.DIMENSION_QUARANTINED_KEY: "",
}
actual, err := strconv.ParseFloat(metrics_util.GetRecordedMetric(t, MEASUREMENT_SWARM_BOTS_DEVICE_TEMP, tags), 64)
require.NoError(t, err)
require.Equalf(t, v, actual, "Wrong temperature seen for metric %s - %s", MEASUREMENT_SWARM_BOTS_DEVICE_TEMP, z)
}
expected = map[string]float64{
"battery_direct": 25.0,
"merble": 2879.0,
"gerble": 40.0,
"battery": 26.0,
"thermal_zone0": 43.0,
"tsens_tz_sensor1": 37.0,
"tsens_tz_sensor2": 41.0,
}
for z, v := range expected {
tags := map[string]string{
"bot": "my-bot-device",
"pool": MOCK_POOL,
"swarming": MOCK_SERVER,
"temp_zone": z,
swarming.DIMENSION_OS_KEY: "",
swarming.DIMENSION_DEVICE_TYPE_KEY: "",
swarming.DIMENSION_DEVICE_OS_KEY: "",
swarming.DIMENSION_QUARANTINED_KEY: "",
}
actual, err := strconv.ParseFloat(metrics_util.GetRecordedMetric(t, MEASUREMENT_SWARM_BOTS_DEVICE_TEMP, tags), 64)
require.NoError(t, err)
require.Equalf(t, v, actual, "Wrong temperature seen for metric %s - %s", MEASUREMENT_SWARM_BOTS_DEVICE_TEMP, z)
}
}
func TestBotUptimeMetrics(t *testing.T) {
unittest.SmallTest(t)
ms := swarming.NewMockApiClient()
defer ms.AssertExpectations(t)
now := time.Date(2017, 9, 1, 12, 0, 0, 0, time.UTC)
ms.On("ListBotsForPool", MOCK_POOL).Return([]*swarming_api.SwarmingRpcsBotInfo{
{
BotId: "my-bot",
LastSeenTs: now.Add(-2 * time.Minute).Format("2006-01-02T15:04:05"),
State: `{"uptime": 153}`,
},
}, nil)
ms.On("ListBotTasks", mock.AnythingOfType("string"), 1).Return([]*swarming_api.SwarmingRpcsTaskResult{
{
ModifiedTs: now.Add(-31 * time.Minute).Format("2006-01-02T15:04:05"),
},
}, nil)
pc := getPromClient()
_, err := reportBotMetrics(now, ms, pc, MOCK_POOL, MOCK_SERVER)
require.NoError(t, err)
tags := map[string]string{}
actual := metrics_util.GetRecordedMetric(t, MEASUREMENT_SWARM_BOTS_UPTIME, tags)
require.Equal(t, "153", actual)
}