package metrics

import (
	"encoding/xml"
	"log"
	"strconv"

	"deevirt.fr/compute/pkg/config"
	"deevirt.fr/compute/pkg/schema"
	"github.com/prometheus/client_golang/prometheus"
	"libvirt.org/go/libvirt"
)

var (
	// Domain CPU
	libvirtDomainInfoMaxMemBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain", "memory_maximum_allocated_bytes"),
		"Maximum allowed memory of the domain, in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainInfoMemoryUsageBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain", "memory_allocated_bytes"),
		"Memory usage of the domain, in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainInfoNrVirtCPU = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain", "virtual_cpus"),
		"Number of virtual CPUs for the domain.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainInfoCPUTime = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain", "cpu_time_seconds_total"),
		"Amount of CPU time used by the domain, in seconds.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainInfoVirDomainState = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain", "state"),
		"Virtual domain state. 0: no state, 1: the domain is running, 2: the domain is blocked on resource,"+
			" 3: the domain is paused by user, 4: the domain is being shut down, 5: the domain is shut off,"+
			"6: the domain is crashed, 7: the domain is suspended by guest power management",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)

	// VCPU
	libvirtDomainVcpuState = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_vcpu", "state"),
		"VCPU state. 0: offline, 1: running, 2: blocked",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "vcpu"},
		nil)
	libvirtDomainVcpuTime = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_vcpu", "time_seconds_total"),
		"Amount of CPU time used by the domain's VCPU, in seconds.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "vcpu"},
		nil)
	libvirtDomainVcpuWait = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_vcpu", "wait_seconds_total"),
		"Vcpu's wait_sum metric. CONFIG_SCHEDSTATS has to be enabled",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "vcpu"},
		nil)
	libvirtDomainVcpuDelay = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_vcpu", "delay_seconds_total"),
		"Amount of CPU time used by the domain's VCPU, in seconds. "+
			"Vcpu's delay metric. Time the vcpu thread was enqueued by the "+
			"host scheduler, but was waiting in the queue instead of running. "+
			"Exposed to the VM as a steal time.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "vcpu"},
		nil)

	// Domain Balloon
	libvirtDomainBalloonStatCurrentBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "current_bytes"),
		"The memory currently used (in bytes).",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatMaximumBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "maximum_bytes"),
		"The maximum memory (in bytes).",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatSwapInBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "swap_in_bytes"),
		"The amount of data read from swap space (in bytes).",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatSwapOutBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "swap_out_bytes"),
		"The amount of memory written out to swap space (in bytes).",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatMajorFaultTotal = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "major_fault_total"),
		"Page faults occur when a process makes a valid access to virtual memory that is not available. "+
			"When servicing the page fault, if disk IO is required, it is considered a major fault.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatMinorFaultTotal = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "minor_fault_total"),
		"Page faults occur when a process makes a valid access to virtual memory that is not available. "+
			"When servicing the page not fault, if disk IO is required, it is considered a minor fault.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatUnusedBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "unused_bytes"),
		"The amount of memory left completely unused by the system. Memory that is available but used for "+
			"reclaimable caches should NOT be reported as free. This value is expressed in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatAvailableBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "available_bytes"),
		"The total amount of usable memory as seen by the domain. This value may be less than the amount of "+
			"memory assigned to the domain if a balloon driver is in use or if the guest OS does not initialize all "+
			"assigned pages. This value is expressed in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatRssBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "rss_bytes"),
		"Resident Set Size of the process running the domain. This value is in bytes",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatUsableBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "usable_bytes"),
		"How much the balloon can be inflated without pushing the guest system to swap, corresponds "+
			"to 'Available' in /proc/meminfo",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)
	libvirtDomainBalloonStatDiskCachesBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_balloon", "disk_cache_bytes"),
		"The amount of memory, that can be quickly reclaimed without additional I/O (in bytes)."+
			"Typically these pages are used for caching files from disk.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id"},
		nil)

	// Domain Block
	libvirtDomainBlockRdBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "read_bytes_total"),
		"Number of bytes read from a block device, in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockRdReq = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "read_requests_total"),
		"Number of read requests from a block device.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockRdTotalTimeSeconds = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "read_time_seconds_total"),
		"Total time spent on reads from a block device, in seconds.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockWrBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "write_bytes_total"),
		"Number of bytes written to a block device, in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockWrReq = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "write_requests_total"),
		"Number of write requests to a block device.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockWrTotalTimes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "write_time_seconds_total"),
		"Total time spent on writes on a block device, in seconds",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockFlushReq = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "flush_requests_total"),
		"Total flush requests from a block device.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockFlushTotalTimeSeconds = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "flush_time_seconds_total"),
		"Total time in seconds spent on cache flushing to a block device",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockAllocation = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "allocation"),
		"Offset of the highest written sector on a block device.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockCapacityBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "capacity_bytes"),
		"Logical size in bytes of the block device	backing image.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainBlockPhysicalSizeBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_block", "physicalsize_bytes"),
		"Physical size in bytes of the container of the backing image.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)

	// Domain Net
	libvirtDomainInterfaceRxBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "receive_bytes_total"),
		"Number of bytes received on a network interface, in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainInterfaceRxPackets = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "receive_packets_total"),
		"Number of packets received on a network interface.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainInterfaceRxErrs = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "receive_errors_total"),
		"Number of packet receive errors on a network interface.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainInterfaceRxDrop = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "receive_drops_total"),
		"Number of packet receive drops on a network interface.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainInterfaceTxBytes = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "transmit_bytes_total"),
		"Number of bytes transmitted on a network interface, in bytes.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainInterfaceTxPackets = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "transmit_packets_total"),
		"Number of packets transmitted on a network interface.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainInterfaceTxErrs = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "transmit_errors_total"),
		"Number of packet transmit errors on a network interface.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
	libvirtDomainInterfaceTxDrop = prometheus.NewDesc(
		prometheus.BuildFQName("libvirt", "domain_interface", "transmit_drops_total"),
		"Number of packet transmit drops on a network interface.",
		[]string{"cluster_id", "company_id", "datacenter_id", "domain_id", "target_device"},
		nil)
)

func CollectDomain(ch chan<- prometheus.Metric, stat libvirt.DomainStats, hostname string) error {
	config, err := config.NewConfig()
	if err != nil {
		log.Fatalln(err)
	}

	domainUUID, err := stat.Domain.GetUUIDString()
	if err != nil {
		return err
	}

	// Decode XML description of domain to get block device names, etc.
	xmlDesc, err := stat.Domain.GetXMLDesc(0)
	if err != nil {
		return err
	}
	var desc schema.Domain
	err = xml.Unmarshal([]byte(xmlDesc), &desc)
	if err != nil {
		return err
	}

	// Report domain info.
	info, err := stat.Domain.GetInfo()
	if err != nil {
		return err
	}

	ch <- prometheus.MustNewConstMetric(
		libvirtDomainInfoMaxMemBytes,
		prometheus.GaugeValue,
		float64(info.MaxMem)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainInfoMemoryUsageBytes,
		prometheus.GaugeValue,
		float64(info.Memory)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainInfoNrVirtCPU,
		prometheus.GaugeValue,
		float64(info.NrVirtCpu),
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainInfoCPUTime,
		prometheus.CounterValue,
		float64(info.CpuTime)/1e9, // From nsec to sec
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainInfoVirDomainState,
		prometheus.GaugeValue,
		float64(info.State),
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)

	// Block Stats
	CollectDomainVCPU(ch, stat.Vcpu, hostname, domainUUID, config, desc)
	CollectDomainBlock(ch, stat.Block, hostname, domainUUID, config, desc)
	CollectDomainNet(ch, stat.Net, hostname, domainUUID, config, desc)
	CollectDomainBalloon(ch, stat.Balloon, hostname, domainUUID, config, desc)

	return nil
}

func CollectDomainVCPU(ch chan<- prometheus.Metric, stat []libvirt.DomainStatsVcpu, hostname string, domainUUID string, config *config.Config, desc schema.Domain) {
	for idx, vcpu := range stat {
		ch <- prometheus.MustNewConstMetric(
			libvirtDomainVcpuState,
			prometheus.GaugeValue,
			float64(vcpu.State),
			config.ClusterID,
			desc.Metadata.DeevirtInstance.DeevirtCompanyID,
			desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
			domainUUID,
			strconv.FormatInt(int64(idx), 10))
		ch <- prometheus.MustNewConstMetric(
			libvirtDomainVcpuTime,
			prometheus.CounterValue,
			float64(vcpu.Time)/1000/1000/1000, // From nsec to sec
			config.ClusterID,
			desc.Metadata.DeevirtInstance.DeevirtCompanyID,
			desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
			domainUUID,
			strconv.FormatInt(int64(idx), 10))
		ch <- prometheus.MustNewConstMetric(
			libvirtDomainVcpuWait,
			prometheus.CounterValue,
			float64(vcpu.Wait)/1e9, // From nsec to sec
			config.ClusterID,
			desc.Metadata.DeevirtInstance.DeevirtCompanyID,
			desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
			domainUUID,
			strconv.FormatInt(int64(idx), 10))
		ch <- prometheus.MustNewConstMetric(
			libvirtDomainVcpuDelay,
			prometheus.CounterValue,
			float64(vcpu.Delay)/1e9, // From nsec to sec
			config.ClusterID,
			desc.Metadata.DeevirtInstance.DeevirtCompanyID,
			desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
			domainUUID,
			strconv.FormatInt(int64(idx), 10))

	}
}

func CollectDomainBalloon(ch chan<- prometheus.Metric, stat *libvirt.DomainStatsBalloon, hostname string, domainUUID string, config *config.Config, desc schema.Domain) {
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatCurrentBytes,
		prometheus.GaugeValue,
		float64(stat.Current)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatMaximumBytes,
		prometheus.GaugeValue,
		float64(stat.Maximum)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatSwapInBytes,
		prometheus.GaugeValue,
		float64(stat.SwapIn)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatSwapOutBytes,
		prometheus.GaugeValue,
		float64(stat.SwapOut)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatMajorFaultTotal,
		prometheus.CounterValue,
		float64(stat.MajorFault),
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatMinorFaultTotal,
		prometheus.CounterValue,
		float64(stat.MinorFault),
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatUnusedBytes,
		prometheus.GaugeValue,
		float64(stat.Unused)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatAvailableBytes,
		prometheus.GaugeValue,
		float64(stat.Available)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatRssBytes,
		prometheus.GaugeValue,
		float64(stat.Rss)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatUsableBytes,
		prometheus.GaugeValue,
		float64(stat.Usable)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)
	ch <- prometheus.MustNewConstMetric(
		libvirtDomainBalloonStatDiskCachesBytes,
		prometheus.GaugeValue,
		float64(stat.DiskCaches)*1024,
		config.ClusterID,
		desc.Metadata.DeevirtInstance.DeevirtCompanyID,
		desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
		domainUUID)

}

func CollectDomainBlock(ch chan<- prometheus.Metric, stat []libvirt.DomainStatsBlock, hostname string, domainUUID string, config *config.Config, desc schema.Domain) {
	for _, block := range stat {

		if block.RdBytesSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockRdBytes,
				prometheus.CounterValue,
				float64(block.RdBytes),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.RdReqsSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockRdReq,
				prometheus.CounterValue,
				float64(block.RdReqs),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.RdTimesSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockRdTotalTimeSeconds,
				prometheus.CounterValue,
				float64(block.RdTimes)/1e9,
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.WrBytesSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockWrBytes,
				prometheus.CounterValue,
				float64(block.WrBytes),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.WrReqsSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockWrReq,
				prometheus.CounterValue,
				float64(block.WrReqs),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.WrTimesSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockWrTotalTimes,
				prometheus.CounterValue,
				float64(block.WrTimes)/1e9,
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.FlReqsSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockFlushReq,
				prometheus.CounterValue,
				float64(block.FlReqs),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.FlTimesSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockFlushTotalTimeSeconds,
				prometheus.CounterValue,
				float64(block.FlTimes)/1e9,
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.AllocationSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockAllocation,
				prometheus.GaugeValue,
				float64(block.Allocation),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.CapacitySet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockCapacityBytes,
				prometheus.GaugeValue,
				float64(block.Capacity),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
		if block.PhysicalSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainBlockPhysicalSizeBytes,
				prometheus.GaugeValue,
				float64(block.Physical),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				block.Name)
		}
	}
}

func CollectDomainNet(ch chan<- prometheus.Metric, stat []libvirt.DomainStatsNet, hostname string, domainUUID string, config *config.Config, desc schema.Domain) {
	for _, iface := range stat {

		if iface.RxBytesSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceRxBytes,
				prometheus.CounterValue,
				float64(iface.RxBytes),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
		if iface.RxPktsSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceRxPackets,
				prometheus.CounterValue,
				float64(iface.RxPkts),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
		if iface.RxErrsSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceRxErrs,
				prometheus.CounterValue,
				float64(iface.RxErrs),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
		if iface.RxDropSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceRxDrop,
				prometheus.CounterValue,
				float64(iface.RxDrop),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
		if iface.TxBytesSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceTxBytes,
				prometheus.CounterValue,
				float64(iface.TxBytes),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
		if iface.TxPktsSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceTxPackets,
				prometheus.CounterValue,
				float64(iface.TxPkts),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
		if iface.TxErrsSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceTxErrs,
				prometheus.CounterValue,
				float64(iface.TxErrs),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
		if iface.TxDropSet {
			ch <- prometheus.MustNewConstMetric(
				libvirtDomainInterfaceTxDrop,
				prometheus.CounterValue,
				float64(iface.TxDrop),
				config.ClusterID,
				desc.Metadata.DeevirtInstance.DeevirtCompanyID,
				desc.Metadata.DeevirtInstance.DeevirtDatacenterID,
				domainUUID,
				iface.Name)
		}
	}
}

func CollectDomains(conn *libvirt.Connect, ch chan<- prometheus.Metric, hostname string) error {

	stats, err := conn.GetAllDomainStats([]*libvirt.Domain{},
		libvirt.DOMAIN_STATS_STATE|libvirt.DOMAIN_STATS_CPU_TOTAL|libvirt.DOMAIN_STATS_BALLOON|
			libvirt.DOMAIN_STATS_VCPU|libvirt.DOMAIN_STATS_BLOCK|libvirt.DOMAIN_STATS_INTERFACE,
		libvirt.CONNECT_GET_ALL_DOMAINS_STATS_RUNNING|libvirt.CONNECT_GET_ALL_DOMAINS_STATS_SHUTOFF)
	defer func(stats []libvirt.DomainStats) {
		for _, stat := range stats {
			stat.Domain.Free()
		}
	}(stats)
	if err != nil {
		return err
	}
	for _, stat := range stats {
		err = CollectDomain(ch, stat, hostname)
		if err != nil {
			log.Printf("Failed to scrape metrics: %s", err)
		}
	}

	return nil
}

func (e *LibvirtExporter) DescribeDomains(ch chan<- *prometheus.Desc) {
	// Domain info
	ch <- libvirtDomainInfoMaxMemBytes
	ch <- libvirtDomainInfoMemoryUsageBytes
	ch <- libvirtDomainInfoNrVirtCPU
	ch <- libvirtDomainInfoCPUTime
	ch <- libvirtDomainInfoVirDomainState

	// VCPU info
	ch <- libvirtDomainVcpuState
	ch <- libvirtDomainVcpuTime
	ch <- libvirtDomainVcpuWait
	ch <- libvirtDomainVcpuDelay

	// Domain Balloon Memory
	ch <- libvirtDomainBalloonStatCurrentBytes
	ch <- libvirtDomainBalloonStatMaximumBytes
	ch <- libvirtDomainBalloonStatSwapInBytes
	ch <- libvirtDomainBalloonStatSwapOutBytes
	ch <- libvirtDomainBalloonStatMajorFaultTotal
	ch <- libvirtDomainBalloonStatMinorFaultTotal
	ch <- libvirtDomainBalloonStatUnusedBytes
	ch <- libvirtDomainBalloonStatAvailableBytes
	ch <- libvirtDomainBalloonStatRssBytes
	ch <- libvirtDomainBalloonStatUsableBytes
	ch <- libvirtDomainBalloonStatDiskCachesBytes

	// Domain block stats
	ch <- libvirtDomainBlockRdBytes
	ch <- libvirtDomainBlockRdReq
	ch <- libvirtDomainBlockRdTotalTimeSeconds
	ch <- libvirtDomainBlockWrBytes
	ch <- libvirtDomainBlockWrReq
	ch <- libvirtDomainBlockWrTotalTimes
	ch <- libvirtDomainBlockFlushReq
	ch <- libvirtDomainBlockFlushTotalTimeSeconds
	ch <- libvirtDomainBlockAllocation
	ch <- libvirtDomainBlockCapacityBytes
	ch <- libvirtDomainBlockPhysicalSizeBytes

	// Domain net interfaces stats
	ch <- libvirtDomainInterfaceRxBytes
	ch <- libvirtDomainInterfaceRxPackets
	ch <- libvirtDomainInterfaceRxErrs
	ch <- libvirtDomainInterfaceRxDrop
	ch <- libvirtDomainInterfaceTxBytes
	ch <- libvirtDomainInterfaceTxPackets
	ch <- libvirtDomainInterfaceTxErrs
	ch <- libvirtDomainInterfaceTxDrop
}
