package libvirt

import (
	"errors"
	"testing"
	"time"

	golibvirt "github.com/digitalocean/go-libvirt"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/metric"
	"github.com/influxdata/telegraf/testutil"
)

func TestLibvirt_Init(t *testing.T) {
	t.Run("throw error when user provided duplicated state metric name", func(t *testing.T) {
		l := Libvirt{
			StatisticsGroups: []string{"state", "state"},
			Log:              testutil.Logger{},
		}
		err := l.Init()
		require.Error(t, err)
		require.Contains(t, err.Error(), "duplicated statistics group in config")
	})

	t.Run("throw error when user provided wrong metric name", func(t *testing.T) {
		l := Libvirt{
			StatisticsGroups: []string{"statusQvo"},
			Log:              testutil.Logger{},
		}
		err := l.Init()
		require.Error(t, err)
		require.Contains(t, err.Error(), "unrecognized metrics name")
	})

	t.Run("throw error when user provided invalid uri", func(t *testing.T) {
		mockUtils := mockLibvirtUtils{}
		l := Libvirt{
			LibvirtURI: "this/is/wrong/uri",
			utils:      &mockUtils,
			Log:        testutil.Logger{},
		}
		err := l.Init()
		require.Error(t, err)
		require.Contains(t, err.Error(), "can't parse")
	})

	t.Run("successfully initialize libvirt on correct user input", func(t *testing.T) {
		mockUtils := mockLibvirtUtils{}
		l := Libvirt{
			StatisticsGroups: []string{"state", "cpu_total", "vcpu", "interface"},
			utils:            &mockUtils,
			LibvirtURI:       defaultLibvirtURI,
			Log:              testutil.Logger{},
		}
		err := l.Init()
		require.NoError(t, err)
	})
}

func TestLibvirt_Gather(t *testing.T) {
	t.Run("wrong uri throws error", func(t *testing.T) {
		var acc testutil.Accumulator
		mockUtils := mockLibvirtUtils{}
		l := Libvirt{
			LibvirtURI: "this/is/wrong/uri",
			Log:        testutil.Logger{},
			utils:      &mockUtils,
		}
		mockUtils.On("ensureConnected", mock.Anything).Return(errors.New("failed to connect")).Once()
		err := l.Gather(&acc)
		require.Error(t, err)
		require.Contains(t, err.Error(), "failed to connect")
		mockUtils.AssertExpectations(t)
	})

	t.Run("error when read error happened in gathering domains", func(t *testing.T) {
		var acc testutil.Accumulator
		mockUtils := mockLibvirtUtils{}
		l := Libvirt{
			utils:            &mockUtils,
			Log:              testutil.Logger{},
			StatisticsGroups: []string{"state"},
		}
		mockUtils.On("ensureConnected", mock.Anything).Return(nil).Once().
			On("gatherAllDomains", mock.Anything).Return(nil, errors.New("gather domain error")).Once().
			On("disconnect").Return(nil).Once()

		err := l.Gather(&acc)
		require.Error(t, err)
		require.Contains(t, err.Error(), "gather domain error")
		mockUtils.AssertExpectations(t)
	})

	t.Run("no error when empty list of domains is returned", func(t *testing.T) {
		var acc testutil.Accumulator
		mockUtils := mockLibvirtUtils{}
		l := Libvirt{
			utils:            &mockUtils,
			Log:              testutil.Logger{},
			StatisticsGroups: []string{"state"},
		}
		mockUtils.On("ensureConnected", mock.Anything).Return(nil).Once().
			On("gatherAllDomains", mock.Anything).Return(nil, nil).Once()

		err := l.Gather(&acc)
		require.NoError(t, err)
		mockUtils.AssertExpectations(t)
	})

	t.Run("error when gathering metrics by number", func(t *testing.T) {
		var acc testutil.Accumulator
		mockUtils := mockLibvirtUtils{}
		l := Libvirt{
			utils:            &mockUtils,
			Log:              testutil.Logger{},
			StatisticsGroups: []string{"state"},
		}
		mockUtils.On("ensureConnected", mock.Anything).Return(nil).Once().
			On("gatherAllDomains", mock.Anything).Return(domains, nil).Once().
			On("gatherStatsForDomains", mock.Anything, mock.Anything).
			Return(nil, errors.New("gathering metric by number error")).Once().
			On("disconnect").Return(nil).Once()

		err := l.Init()
		require.NoError(t, err)

		err = l.Gather(&acc)
		require.Error(t, err)
		require.Contains(t, err.Error(), "gathering metric by number error")
		mockUtils.AssertExpectations(t)
	})

	var successfulTests = []struct {
		testName        string
		allDomains      interface{}
		excludeDomains  []string
		statsForDomains interface{}
		expectedMetrics []telegraf.Metric
		vcpuMapping     []vcpuAffinity
	}{
		{"successfully gather from host that has domains", domains, nil, domainStats, append(expectedMetrics, expectedVcpuAffinityMetrics...), vcpusMapping},
		{
			"successfully gather from host for excluded domain",
			domains,
			[]string{"Droplet-33436"},
			domainStats[1:],
			append(expectedMetrics[1:], expectedVcpuAffinityMetrics[2:]...),
			vcpusMapping,
		},
	}
	for _, test := range successfulTests {
		t.Run(test.testName, func(t *testing.T) {
			var acc testutil.Accumulator
			mockUtils := mockLibvirtUtils{}
			l := Libvirt{
				utils:                &mockUtils,
				Log:                  testutil.Logger{},
				StatisticsGroups:     []string{"state"},
				Domains:              test.excludeDomains,
				AdditionalStatistics: []string{"vcpu_mapping"},
			}
			mockUtils.On("ensureConnected", mock.Anything).Return(nil).Once().
				On("gatherAllDomains", mock.Anything).Return(test.allDomains, nil).Once().
				On("gatherVcpuMapping", domains[0], mock.Anything, mock.Anything).Return(test.vcpuMapping, nil).Maybe().
				On("gatherVcpuMapping", domains[1], mock.Anything, mock.Anything).Return(test.vcpuMapping, nil).Once().
				On("gatherNumberOfPCPUs").Return(4, nil).Once().
				On("gatherStatsForDomains", mock.Anything, mock.Anything).Return(test.statsForDomains, nil).Once()

			err := l.Init()
			require.NoError(t, err)

			err = l.Gather(&acc)
			require.NoError(t, err)

			actual := acc.GetTelegrafMetrics()
			expected := test.expectedMetrics
			testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics(), testutil.IgnoreTime())
			mockUtils.AssertExpectations(t)
		})
	}
}

func TestLibvirt_GatherMetrics(t *testing.T) {
	var successfulTests = []struct {
		testName        string
		allDomains      interface{}
		excludeDomains  []string
		statsForDomains interface{}
		expectedMetrics []telegraf.Metric
		vcpuMapping     []vcpuAffinity
	}{
		{"successfully gather memory metrics from host that has domains", domains, nil, memoryStats, expectedMemoryMetrics, nil},
		{"successfully gather balloon metrics from host that has domains", domains, nil, balloonStats, expectedBalloonMetrics, nil},
		{"successfully gather perf metrics from host that has domains", domains, nil, perfStats, expectedPerfMetrics, nil},
		{"successfully gather cpu metrics from host that has domains", domains, nil, cpuStats, expectedCPUMetrics, nil},
		{"successfully gather interface metrics from host that has domains", domains, nil, interfaceStats, expectedInterfaceMetrics, nil},
		{"successfully gather block metrics from host that has domains", domains, nil, blockStats, expectedBlockMetrics, nil},
		{"successfully gather iothread metrics from host that has domains", domains, nil, iothreadStats, expectedIOThreadMetrics, nil},
		{"successfully gather dirtyrate metrics from host that has domains", domains, nil, dirtyrateStats, expectedDirtyrateMetrics, nil},
		{"successfully gather vcpu metrics from host that has domains", domains, nil, vcpuStats, expectedVCPUMetrics, nil},
		{"successfully gather vcpu metrics with vCPU from host that has domains", domains, nil, vcpuStats, expectedExtendedVCPUMetrics, vcpusMapping},
	}
	for _, test := range successfulTests {
		t.Run(test.testName, func(t *testing.T) {
			var acc testutil.Accumulator
			mockUtils := mockLibvirtUtils{}
			l := Libvirt{
				utils:   &mockUtils,
				Log:     testutil.Logger{},
				Domains: test.excludeDomains,
			}

			mockUtils.On("ensureConnected", mock.Anything).Return(nil).Once().
				On("gatherAllDomains", mock.Anything).Return(test.allDomains, nil).Once().
				On("gatherStatsForDomains", mock.Anything, mock.Anything).Return(test.statsForDomains, nil).Once()

			if test.vcpuMapping != nil {
				l.vcpuMappingEnabled = true
				l.metricNumber = domainStatsVCPU
				mockUtils.On("gatherNumberOfPCPUs").Return(4, nil).Once().
					On("gatherVcpuMapping", domains[0], mock.Anything, mock.Anything).Return(test.vcpuMapping, nil).Once().
					On("gatherVcpuMapping", domains[1], mock.Anything, mock.Anything).Return(nil, nil).Once()
			}

			err := l.Gather(&acc)
			require.NoError(t, err)

			actual := acc.GetTelegrafMetrics()
			expected := test.expectedMetrics
			testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics(), testutil.IgnoreTime())
			mockUtils.AssertExpectations(t)
		})
	}
}

func TestLibvirt_validateLibvirtUri(t *testing.T) {
	t.Run("no error on good uri provided", func(t *testing.T) {
		l := Libvirt{
			LibvirtURI: defaultLibvirtURI,
			Log:        testutil.Logger{},
		}
		err := l.validateLibvirtURI()
		require.NoError(t, err)
	})

	t.Run("unmarshal error on bad uri provided", func(t *testing.T) {
		l := Libvirt{
			LibvirtURI: "this/is/invalid/uri",
			Log:        testutil.Logger{},
		}
		err := l.validateLibvirtURI()
		require.Error(t, err)
		require.Contains(t, err.Error(), "can't parse '"+l.LibvirtURI+"' as a libvirt uri")
	})

	t.Run("dialer error on bad ssh uri provided", func(t *testing.T) {
		l := Libvirt{
			LibvirtURI: "qemu+ssh://invalid@host:666/system",
			Log:        testutil.Logger{},
		}
		err := l.validateLibvirtURI()
		require.Error(t, err)
		require.Contains(t, err.Error(), "ssh transport requires keyfile parameter")
	})
}

func TestLibvirt_calculateMetricNumber(t *testing.T) {
	t.Run("error on duplicated metric name", func(t *testing.T) {
		l := Libvirt{
			StatisticsGroups: []string{"state", "state"},
			Log:              testutil.Logger{},
		}
		err := l.calculateMetricNumber()
		require.Error(t, err)
		require.Contains(t, err.Error(), "duplicated statistics group in config")
	})

	t.Run("error on unrecognized metric name", func(t *testing.T) {
		l := Libvirt{
			StatisticsGroups: []string{"invalidName"},
			Log:              testutil.Logger{},
		}
		err := l.calculateMetricNumber()
		require.Error(t, err)
		require.Contains(t, err.Error(), "unrecognized metrics name")
	})

	t.Run("correctly calculates metrics number provided", func(t *testing.T) {
		metrics := []string{"state", "cpu_total", "vcpu", "interface", "block", "balloon",
			"memory", "perf", "iothread", "dirtyrate"}

		l := Libvirt{
			StatisticsGroups: metrics,
			Log:              testutil.Logger{},
		}
		err := l.calculateMetricNumber()
		require.NoError(t, err)
		require.Equal(t, domainStatsAll, l.metricNumber)
	})
}

func TestLibvirt_filterDomains(t *testing.T) {
	t.Run("success filter domains", func(t *testing.T) {
		l := Libvirt{
			Domains: []string{"Droplet-844329", "Droplet-33436"},
			Log:     testutil.Logger{},
		}

		result := l.filterDomains(domains)
		require.NotEmpty(t, result)
	})
}

var (
	domains = []golibvirt.Domain{
		{Name: "Droplet-844329", UUID: golibvirt.UUID{}, ID: 0},
		{Name: "Droplet-33436", UUID: golibvirt.UUID{}, ID: 0},
	}

	domainStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "state.reason", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "state.state", Value: *golibvirt.NewTypedParamValueLlong(1)},
			},
		},
		{
			Dom: domains[1],
			Params: []golibvirt.TypedParam{
				{Field: "state.reason", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "state.state", Value: *golibvirt.NewTypedParamValueLlong(1)},
			},
		},
	}

	memoryStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "memory.bandwidth.monitor.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "memory.bandwidth.monitor.0.name", Value: *golibvirt.NewTypedParamValueString("any_name_vcpus_0-4")},
				{Field: "memory.bandwidth.monitor.0.vcpus", Value: *golibvirt.NewTypedParamValueString("0-4")},
				{Field: "memory.bandwidth.monitor.0.node.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "memory.bandwidth.monitor.1.name", Value: *golibvirt.NewTypedParamValueString("vcpus_7")},
				{Field: "memory.bandwidth.monitor.1.vcpus", Value: *golibvirt.NewTypedParamValueString("7")},
				{Field: "memory.bandwidth.monitor.1.node.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "memory.bandwidth.monitor.0.node.0.id", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "memory.bandwidth.monitor.0.node.0.bytes.total", Value: *golibvirt.NewTypedParamValueLlong(10208067584)},
				{Field: "memory.bandwidth.monitor.0.node.0.bytes.local", Value: *golibvirt.NewTypedParamValueLlong(4807114752)},
				{Field: "memory.bandwidth.monitor.0.node.1.id", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "memory.bandwidth.monitor.0.node.1.bytes.total", Value: *golibvirt.NewTypedParamValueLlong(8693735424)},
				{Field: "memory.bandwidth.monitor.0.node.1.bytes.local", Value: *golibvirt.NewTypedParamValueLlong(5850161152)},
				{Field: "memory.bandwidth.monitor.1.node.0.id", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "memory.bandwidth.monitor.1.node.0.bytes.total", Value: *golibvirt.NewTypedParamValueLlong(853811200)},
				{Field: "memory.bandwidth.monitor.1.node.0.bytes.local", Value: *golibvirt.NewTypedParamValueLlong(290701312)},
				{Field: "memory.bandwidth.monitor.1.node.1.id", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "memory.bandwidth.monitor.1.node.1.bytes.total", Value: *golibvirt.NewTypedParamValueLlong(406044672)},
				{Field: "memory.bandwidth.monitor.1.node.1.bytes.local", Value: *golibvirt.NewTypedParamValueLlong(229425152)},
			},
		},
	}

	cpuStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "cpu.time", Value: *golibvirt.NewTypedParamValueLlong(67419144867000)},
				{Field: "cpu.user", Value: *golibvirt.NewTypedParamValueLlong(63886161852000)},
				{Field: "cpu.system", Value: *golibvirt.NewTypedParamValueLlong(3532983015000)},
				{Field: "cpu.haltpoll.success.time", Value: *golibvirt.NewTypedParamValueLlong(516907915)},
				{Field: "cpu.haltpoll.fail.time", Value: *golibvirt.NewTypedParamValueLlong(2727253643)},
				{Field: "cpu.cache.monitor.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "cpu.cache.monitor.0.name", Value: *golibvirt.NewTypedParamValueString("any_name_vcpus_0-3")},
				{Field: "cpu.cache.monitor.0.vcpus", Value: *golibvirt.NewTypedParamValueString("0-3")},
				{Field: "cpu.cache.monitor.0.bank.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "cpu.cache.monitor.1.name", Value: *golibvirt.NewTypedParamValueString("vcpus_4-9")},
				{Field: "cpu.cache.monitor.1.vcpus", Value: *golibvirt.NewTypedParamValueString("4-9")},
				{Field: "cpu.cache.monitor.1.bank.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "cpu.cache.monitor.0.bank.0.id", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "cpu.cache.monitor.0.bank.0.bytes", Value: *golibvirt.NewTypedParamValueLlong(5406720)},
				{Field: "cpu.cache.monitor.0.bank.1.id", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "cpu.cache.monitor.0.bank.1.bytes", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "cpu.cache.monitor.1.bank.0.id", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "cpu.cache.monitor.1.bank.0.bytes", Value: *golibvirt.NewTypedParamValueLlong(720896)},
				{Field: "cpu.cache.monitor.1.bank.1.id", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "cpu.cache.monitor.1.bank.1.bytes", Value: *golibvirt.NewTypedParamValueLlong(8200192)},
			},
		},
	}

	balloonStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "balloon.current", Value: *golibvirt.NewTypedParamValueLlong(4194304)},
				{Field: "balloon.maximum", Value: *golibvirt.NewTypedParamValueLlong(4194304)},
				{Field: "balloon.swap_in", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "balloon.swap_out", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "balloon.major_fault", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "balloon.minor_fault", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "balloon.unused", Value: *golibvirt.NewTypedParamValueLlong(3928628)},
				{Field: "balloon.available", Value: *golibvirt.NewTypedParamValueLlong(4018480)},
				{Field: "balloon.rss", Value: *golibvirt.NewTypedParamValueLlong(1036012)},
				{Field: "balloon.usable", Value: *golibvirt.NewTypedParamValueLlong(3808724)},
				{Field: "balloon.last-update", Value: *golibvirt.NewTypedParamValueLlong(1654611373)},
				{Field: "balloon.disk_caches", Value: *golibvirt.NewTypedParamValueLlong(68820)},
				{Field: "balloon.hugetlb_pgalloc", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "balloon.hugetlb_pgfail", Value: *golibvirt.NewTypedParamValueLlong(0)},
			},
		},
	}

	perfStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "perf.cmt", Value: *golibvirt.NewTypedParamValueLlong(19087360)},
				{Field: "perf.mbmt", Value: *golibvirt.NewTypedParamValueLlong(77168640)},
				{Field: "perf.mbml", Value: *golibvirt.NewTypedParamValueLlong(67788800)},
				{Field: "perf.cpu_cycles", Value: *golibvirt.NewTypedParamValueLlong(29858995122)},
				{Field: "perf.instructions", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "perf.cache_references", Value: *golibvirt.NewTypedParamValueLlong(3053301695)},
				{Field: "perf.cache_misses", Value: *golibvirt.NewTypedParamValueLlong(609441024)},
				{Field: "perf.branch_instructions", Value: *golibvirt.NewTypedParamValueLlong(2623890194)},
				{Field: "perf.branch_misses", Value: *golibvirt.NewTypedParamValueLlong(103707961)},
				{Field: "perf.bus_cycles", Value: *golibvirt.NewTypedParamValueLlong(188105628)},
				{Field: "perf.stalled_cycles_frontend", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "perf.stalled_cycles_backend", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "perf.ref_cpu_cycles", Value: *golibvirt.NewTypedParamValueLlong(30766094039)},
				{Field: "perf.cpu_clock", Value: *golibvirt.NewTypedParamValueLlong(25166642695)},
				{Field: "perf.task_clock", Value: *golibvirt.NewTypedParamValueLlong(25263578917)},
				{Field: "perf.page_faults", Value: *golibvirt.NewTypedParamValueLlong(2670)},
				{Field: "perf.context_switches", Value: *golibvirt.NewTypedParamValueLlong(294284)},
				{Field: "perf.cpu_migrations", Value: *golibvirt.NewTypedParamValueLlong(17949)},
				{Field: "perf.page_faults_min", Value: *golibvirt.NewTypedParamValueLlong(2670)},
				{Field: "perf.page_faults_maj", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "perf.alignment_faults", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "perf.emulation_faults", Value: *golibvirt.NewTypedParamValueLlong(0)},
			},
		},
	}

	interfaceStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "net.count", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "net.0.name", Value: *golibvirt.NewTypedParamValueString("vnet0")},
				{Field: "net.0.rx.bytes", Value: *golibvirt.NewTypedParamValueLlong(110)},
				{Field: "net.0.rx.pkts", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "net.0.rx.errs", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "net.0.rx.drop", Value: *golibvirt.NewTypedParamValueLlong(31007)},
				{Field: "net.0.tx.bytes", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "net.0.tx.pkts", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "net.0.tx.errs", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "net.0.tx.drop", Value: *golibvirt.NewTypedParamValueLlong(0)},
			},
		},
	}

	blockStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "block.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "block.0.name", Value: *golibvirt.NewTypedParamValueString("vda")},
				{Field: "block.0.backingIndex", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "block.0.path", Value: *golibvirt.NewTypedParamValueString("/tmp/ubuntu_image.img")},
				{Field: "block.0.rd.reqs", Value: *golibvirt.NewTypedParamValueLlong(11354)},
				{Field: "block.0.rd.bytes", Value: *golibvirt.NewTypedParamValueLlong(330314752)},
				{Field: "block.0.rd.times", Value: *golibvirt.NewTypedParamValueLlong(6240559566)},
				{Field: "block.0.wr.reqs", Value: *golibvirt.NewTypedParamValueLlong(52440)},
				{Field: "block.0.wr.bytes", Value: *golibvirt.NewTypedParamValueLlong(1183828480)},
				{Field: "block.0.wr.times", Value: *golibvirt.NewTypedParamValueLlong(21887150375)},
				{Field: "block.0.fl.reqs", Value: *golibvirt.NewTypedParamValueLlong(32250)},
				{Field: "block.0.fl.times", Value: *golibvirt.NewTypedParamValueLlong(23158998353)},
				{Field: "block.0.errors", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "block.0.allocation", Value: *golibvirt.NewTypedParamValueLlong(770048000)},
				{Field: "block.0.capacity", Value: *golibvirt.NewTypedParamValueLlong(2361393152)},
				{Field: "block.0.physical", Value: *golibvirt.NewTypedParamValueLlong(770052096)},
				{Field: "block.0.threshold", Value: *golibvirt.NewTypedParamValueLlong(2147483648)},
				{Field: "block.1.name", Value: *golibvirt.NewTypedParamValueString("vda1")},
				{Field: "block.1.backingIndex", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "block.1.path", Value: *golibvirt.NewTypedParamValueString("/tmp/ubuntu_image1.img")},
				{Field: "block.1.rd.reqs", Value: *golibvirt.NewTypedParamValueLlong(11354)},
				{Field: "block.1.rd.bytes", Value: *golibvirt.NewTypedParamValueLlong(330314752)},
				{Field: "block.1.rd.times", Value: *golibvirt.NewTypedParamValueLlong(6240559566)},
				{Field: "block.1.wr.reqs", Value: *golibvirt.NewTypedParamValueLlong(52440)},
				{Field: "block.1.wr.bytes", Value: *golibvirt.NewTypedParamValueLlong(1183828480)},
				{Field: "block.1.wr.times", Value: *golibvirt.NewTypedParamValueLlong(21887150375)},
				{Field: "block.1.fl.reqs", Value: *golibvirt.NewTypedParamValueLlong(32250)},
				{Field: "block.1.fl.times", Value: *golibvirt.NewTypedParamValueLlong(23158998353)},
				{Field: "block.1.errors", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "block.1.allocation", Value: *golibvirt.NewTypedParamValueLlong(770048000)},
				{Field: "block.1.capacity", Value: *golibvirt.NewTypedParamValueLlong(2361393152)},
				{Field: "block.1.physical", Value: *golibvirt.NewTypedParamValueLlong(770052096)},
				{Field: "block.1.threshold", Value: *golibvirt.NewTypedParamValueLlong(2147483648)},
			},
		},
	}

	iothreadStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "iothread.count", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "iothread.0.poll-max-ns", Value: *golibvirt.NewTypedParamValueLlong(32768)},
				{Field: "iothread.0.poll-grow", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "iothread.0.poll-shrink", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "iothread.1.poll-max-ns", Value: *golibvirt.NewTypedParamValueLlong(32769)},
				{Field: "iothread.1.poll-grow", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "iothread.1.poll-shrink", Value: *golibvirt.NewTypedParamValueLlong(0)},
			},
		},
	}

	dirtyrateStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "dirtyrate.calc_status", Value: *golibvirt.NewTypedParamValueLlong(2)},
				{Field: "dirtyrate.calc_start_time", Value: *golibvirt.NewTypedParamValueLlong(348414)},
				{Field: "dirtyrate.calc_period", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "dirtyrate.megabytes_per_second", Value: *golibvirt.NewTypedParamValueLlong(4)},
				{Field: "dirtyrate.calc_mode", Value: *golibvirt.NewTypedParamValueString("dirty-ring")},
				{Field: "dirtyrate.vcpu.0.megabytes_per_second", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "dirtyrate.vcpu.1.megabytes_per_second", Value: *golibvirt.NewTypedParamValueLlong(2)},
			},
		},
	}

	vcpuStats = []golibvirt.DomainStatsRecord{
		{
			Dom: domains[0],
			Params: []golibvirt.TypedParam{
				{Field: "vcpu.current", Value: *golibvirt.NewTypedParamValueLlong(3)},
				{Field: "vcpu.maximum", Value: *golibvirt.NewTypedParamValueLlong(3)},
				{Field: "vcpu.0.state", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "vcpu.0.time", Value: *golibvirt.NewTypedParamValueLlong(17943740000000)},
				{Field: "vcpu.0.wait", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "vcpu.0.halted", Value: *golibvirt.NewTypedParamValueString("no")},
				{Field: "vcpu.0.delay", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "vcpu.1.state", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "vcpu.1.time", Value: *golibvirt.NewTypedParamValueLlong(17943740000000)},
				{Field: "vcpu.1.wait", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "vcpu.1.halted", Value: *golibvirt.NewTypedParamValueString("yes")},
				{Field: "vcpu.1.delay", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "vcpu.2.state", Value: *golibvirt.NewTypedParamValueLlong(1)},
				{Field: "vcpu.2.time", Value: *golibvirt.NewTypedParamValueLlong(17943740000000)},
				{Field: "vcpu.2.wait", Value: *golibvirt.NewTypedParamValueLlong(0)},
				{Field: "vcpu.2.delay", Value: *golibvirt.NewTypedParamValueLlong(0)},
			},
		},
	}

	vcpusMapping = []vcpuAffinity{
		{"0", "0,1,2,3", 0},
		{"1", "1,2,3,4", 1},
	}

	expectedMetrics = []telegraf.Metric{
		metric.New("libvirt_state",
			map[string]string{"domain_name": "Droplet-844329"},
			map[string]interface{}{
				"reason": 2,
				"state":  1,
			},
			time.Now()),
		metric.New("libvirt_state",
			map[string]string{"domain_name": "Droplet-33436"},
			map[string]interface{}{
				"reason": 1,
				"state":  1,
			},
			time.Now()),
	}

	expectedMemoryMetrics = []telegraf.Metric{
		metric.New("libvirt_memory_bandwidth_monitor_total",
			map[string]string{"domain_name": "Droplet-844329"},
			map[string]interface{}{
				"count": 2,
			},
			time.Now()),
		metric.New("libvirt_memory_bandwidth_monitor",
			map[string]string{"domain_name": "Droplet-844329", "memory_bandwidth_monitor_id": "0"},
			map[string]interface{}{
				"name":       "any_name_vcpus_0-4",
				"vcpus":      "0-4",
				"node_count": 2,
			},
			time.Now()),
		metric.New("libvirt_memory_bandwidth_monitor",
			map[string]string{"domain_name": "Droplet-844329", "memory_bandwidth_monitor_id": "1"},
			map[string]interface{}{
				"name":       "vcpus_7",
				"vcpus":      "7",
				"node_count": 2,
			},
			time.Now()),
		metric.New("libvirt_memory_bandwidth_monitor_node",
			map[string]string{"domain_name": "Droplet-844329", "memory_bandwidth_monitor_id": "0", "controller_index": "0"},
			map[string]interface{}{
				"id":          0,
				"bytes_total": int64(10208067584),
				"bytes_local": int64(4807114752),
			},
			time.Now()),
		metric.New("libvirt_memory_bandwidth_monitor_node",
			map[string]string{"domain_name": "Droplet-844329", "memory_bandwidth_monitor_id": "0", "controller_index": "1"},
			map[string]interface{}{
				"id":          1,
				"bytes_total": int64(8693735424),
				"bytes_local": int64(5850161152),
			},
			time.Now()),
		metric.New("libvirt_memory_bandwidth_monitor_node",
			map[string]string{"domain_name": "Droplet-844329", "memory_bandwidth_monitor_id": "1", "controller_index": "0"},
			map[string]interface{}{
				"id":          0,
				"bytes_total": 853811200,
				"bytes_local": 290701312,
			},
			time.Now()),
		metric.New("libvirt_memory_bandwidth_monitor_node",
			map[string]string{"domain_name": "Droplet-844329", "memory_bandwidth_monitor_id": "1", "controller_index": "1"},
			map[string]interface{}{
				"id":          1,
				"bytes_total": 406044672,
				"bytes_local": 229425152,
			},
			time.Now()),
	}

	expectedCPUMetrics = []telegraf.Metric{
		metric.New("libvirt_cpu",
			map[string]string{"domain_name": "Droplet-844329"},
			map[string]interface{}{
				"time":                  int64(67419144867000),
				"user":                  int64(63886161852000),
				"system":                int64(3532983015000),
				"haltpoll_success_time": 516907915,
				"haltpoll_fail_time":    int64(2727253643),
			},
			time.Now()),
		metric.New("libvirt_cpu_cache_monitor_total",
			map[string]string{"domain_name": "Droplet-844329"},
			map[string]interface{}{
				"count": 2,
			},
			time.Now()),
		metric.New("libvirt_cpu_cache_monitor",
			map[string]string{"domain_name": "Droplet-844329", "cache_monitor_id": "0"},
			map[string]interface{}{
				"name":       "any_name_vcpus_0-3",
				"vcpus":      "0-3",
				"bank_count": 2,
			},
			time.Now()),
		metric.New("libvirt_cpu_cache_monitor",
			map[string]string{"domain_name": "Droplet-844329", "cache_monitor_id": "1"},
			map[string]interface{}{
				"name":       "vcpus_4-9",
				"vcpus":      "4-9",
				"bank_count": 2,
			},
			time.Now()),
		metric.New("libvirt_cpu_cache_monitor_bank",
			map[string]string{"domain_name": "Droplet-844329", "cache_monitor_id": "0", "bank_index": "0"},
			map[string]interface{}{
				"id":    0,
				"bytes": 5406720,
			},
			time.Now()),
		metric.New("libvirt_cpu_cache_monitor_bank",
			map[string]string{"domain_name": "Droplet-844329", "cache_monitor_id": "0", "bank_index": "1"},
			map[string]interface{}{
				"id":    1,
				"bytes": 0,
			},
			time.Now()),
		metric.New("libvirt_cpu_cache_monitor_bank",
			map[string]string{"domain_name": "Droplet-844329", "cache_monitor_id": "1", "bank_index": "0"},
			map[string]interface{}{
				"id":    0,
				"bytes": 720896,
			},
			time.Now()),
		metric.New("libvirt_cpu_cache_monitor_bank",
			map[string]string{"domain_name": "Droplet-844329", "cache_monitor_id": "1", "bank_index": "1"},
			map[string]interface{}{
				"id":    1,
				"bytes": 8200192,
			},
			time.Now()),
	}

	expectedVcpuAffinityMetrics = []telegraf.Metric{
		metric.New("libvirt_cpu_affinity",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "0"},
			map[string]interface{}{
				"cpu_id": "0,1,2,3",
			},
			time.Now()),
		metric.New("libvirt_cpu_affinity",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "1"},
			map[string]interface{}{
				"cpu_id": "1,2,3,4",
			},
			time.Now()),
		metric.New("libvirt_cpu_affinity",
			map[string]string{
				"domain_name": "Droplet-33436",
				"vcpu_id":     "0"},
			map[string]interface{}{
				"cpu_id": "0,1,2,3",
			},
			time.Now()),
		metric.New("libvirt_cpu_affinity",
			map[string]string{
				"domain_name": "Droplet-33436",
				"vcpu_id":     "1"},
			map[string]interface{}{
				"cpu_id": "1,2,3,4",
			},
			time.Now()),
	}

	expectedBalloonMetrics = []telegraf.Metric{
		metric.New("libvirt_balloon",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"current":         4194304,
				"maximum":         4194304,
				"swap_in":         0,
				"swap_out":        0,
				"major_fault":     0,
				"minor_fault":     0,
				"unused":          3928628,
				"available":       4018480,
				"rss":             1036012,
				"usable":          3808724,
				"last_update":     1654611373,
				"disk_caches":     68820,
				"hugetlb_pgalloc": 0,
				"hugetlb_pgfail":  0,
			},
			time.Now()),
	}

	expectedPerfMetrics = []telegraf.Metric{
		metric.New("libvirt_perf",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"cmt":                     19087360,
				"mbmt":                    77168640,
				"mbml":                    67788800,
				"cpu_cycles":              int64(29858995122),
				"instructions":            0,
				"cache_references":        int64(3053301695),
				"cache_misses":            609441024,
				"branch_instructions":     int64(2623890194),
				"branch_misses":           103707961,
				"bus_cycles":              188105628,
				"stalled_cycles_frontend": 0,
				"stalled_cycles_backend":  0,
				"ref_cpu_cycles":          int64(30766094039),
				"cpu_clock":               int64(25166642695),
				"task_clock":              int64(25263578917),
				"page_faults":             2670,
				"context_switches":        294284,
				"cpu_migrations":          17949,
				"page_faults_min":         2670,
				"page_faults_maj":         0,
				"alignment_faults":        0,
				"emulation_faults":        0,
			},
			time.Now()),
	}

	expectedInterfaceMetrics = []telegraf.Metric{
		metric.New("libvirt_net_total",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"count": 1,
			},
			time.Now()),
		metric.New("libvirt_net",
			map[string]string{
				"domain_name":  "Droplet-844329",
				"interface_id": "0",
			},
			map[string]interface{}{
				"name":     "vnet0",
				"rx_bytes": 110,
				"rx_pkts":  1,
				"rx_errs":  0,
				"rx_drop":  31007,
				"tx_bytes": 0,
				"tx_pkts":  0,
				"tx_errs":  0,
				"tx_drop":  0,
			},
			time.Now()),
	}

	expectedBlockMetrics = []telegraf.Metric{
		metric.New("libvirt_block_total",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"count": 2,
			},
			time.Now()),
		metric.New("libvirt_block",
			map[string]string{
				"domain_name": "Droplet-844329",
				"block_id":    "0",
			},
			map[string]interface{}{
				"name":         "vda",
				"backingIndex": 1,
				"path":         "/tmp/ubuntu_image.img",
				"rd_reqs":      11354,
				"rd_bytes":     330314752,
				"rd_times":     int64(6240559566),
				"wr_reqs":      52440,
				"wr_bytes":     1183828480,
				"wr_times":     int64(21887150375),
				"fl_reqs":      32250,
				"fl_times":     int64(23158998353),
				"errors":       0,
				"allocation":   770048000,
				"capacity":     int64(2361393152),
				"physical":     770052096,
				"threshold":    int64(2147483648),
			},
			time.Now()),
		metric.New("libvirt_block",
			map[string]string{
				"domain_name": "Droplet-844329",
				"block_id":    "1",
			},
			map[string]interface{}{
				"name":         "vda1",
				"backingIndex": 1,
				"path":         "/tmp/ubuntu_image1.img",
				"rd_reqs":      11354,
				"rd_bytes":     330314752,
				"rd_times":     int64(6240559566),
				"wr_reqs":      52440,
				"wr_bytes":     1183828480,
				"wr_times":     int64(21887150375),
				"fl_reqs":      32250,
				"fl_times":     int64(23158998353),
				"errors":       0,
				"allocation":   770048000,
				"capacity":     int64(2361393152),
				"physical":     770052096,
				"threshold":    int64(2147483648),
			},
			time.Now()),
	}

	expectedIOThreadMetrics = []telegraf.Metric{
		metric.New("libvirt_iothread_total",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"count": 2,
			},
			time.Now()),
		metric.New("libvirt_iothread",
			map[string]string{
				"domain_name": "Droplet-844329",
				"iothread_id": "0",
			},
			map[string]interface{}{
				"poll_max_ns": 32768,
				"poll_grow":   0,
				"poll_shrink": 0,
			},
			time.Now()),
		metric.New("libvirt_iothread",
			map[string]string{
				"domain_name": "Droplet-844329",
				"iothread_id": "1",
			},
			map[string]interface{}{
				"poll_max_ns": 32769,
				"poll_grow":   0,
				"poll_shrink": 0,
			},
			time.Now()),
	}

	expectedDirtyrateMetrics = []telegraf.Metric{
		metric.New("libvirt_dirtyrate",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"calc_status":          2,
				"calc_start_time":      348414,
				"calc_period":          1,
				"megabytes_per_second": 4,
				"calc_mode":            "dirty-ring",
			},
			time.Now()),
		metric.New("libvirt_dirtyrate_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "0",
			},
			map[string]interface{}{
				"megabytes_per_second": 1,
			},
			time.Now()),
		metric.New("libvirt_dirtyrate_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "1",
			},
			map[string]interface{}{
				"megabytes_per_second": 2,
			},
			time.Now()),
	}

	expectedVCPUMetrics = []telegraf.Metric{
		metric.New("libvirt_vcpu_total",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"current": 3,
				"maximum": 3,
			},
			time.Now()),
		metric.New("libvirt_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "0",
			},
			map[string]interface{}{
				"state":    1,
				"time":     int64(17943740000000),
				"wait":     0,
				"halted":   "no",
				"halted_i": 0,
				"delay":    0,
			},
			time.Now()),
		metric.New("libvirt_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "1",
			},
			map[string]interface{}{
				"state":    1,
				"time":     int64(17943740000000),
				"wait":     0,
				"halted":   "yes",
				"halted_i": 1,
				"delay":    0,
			},
			time.Now()),
		metric.New("libvirt_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "2",
			},
			map[string]interface{}{
				"state": 1,
				"time":  int64(17943740000000),
				"wait":  0,
				"delay": 0,
			},
			time.Now()),
	}

	expectedExtendedVCPUMetrics = []telegraf.Metric{
		metric.New("libvirt_cpu_affinity",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "0"},
			map[string]interface{}{
				"cpu_id": "0,1,2,3",
			},
			time.Now()),
		metric.New("libvirt_cpu_affinity",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "1"},
			map[string]interface{}{
				"cpu_id": "1,2,3,4",
			},
			time.Now()),
		metric.New("libvirt_vcpu_total",
			map[string]string{
				"domain_name": "Droplet-844329",
			},
			map[string]interface{}{
				"current": 3,
				"maximum": 3,
			},
			time.Now()),
		metric.New("libvirt_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "0",
			},
			map[string]interface{}{
				"state":    1,
				"time":     int64(17943740000000),
				"wait":     0,
				"halted":   "no",
				"halted_i": 0,
				"delay":    0,
				"cpu_id":   0,
			},
			time.Now()),
		metric.New("libvirt_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "1",
			},
			map[string]interface{}{
				"state":    1,
				"time":     int64(17943740000000),
				"wait":     0,
				"halted":   "yes",
				"halted_i": 1,
				"delay":    0,
				"cpu_id":   1,
			},
			time.Now()),
		metric.New("libvirt_vcpu",
			map[string]string{
				"domain_name": "Droplet-844329",
				"vcpu_id":     "2",
			},
			map[string]interface{}{
				"state": 1,
				"time":  int64(17943740000000),
				"wait":  0,
				"delay": 0,
			},
			time.Now()),
	}
)
