// Copyright 2014 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package metrics import ( "errors" "io/ioutil" "net/http" "net/http/httptest" "regexp" "strings" "testing" "time" "github.com/google/cadvisor/container" info "github.com/google/cadvisor/info/v1" "github.com/prometheus/client_golang/prometheus" ) type testSubcontainersInfoProvider struct{} func (p testSubcontainersInfoProvider) GetVersionInfo() (*info.VersionInfo, error) { return &info.VersionInfo{ KernelVersion: "4.1.6-200.fc22.x86_64", ContainerOsVersion: "Fedora 22 (Twenty Two)", DockerVersion: "1.8.1", CadvisorVersion: "0.16.0", CadvisorRevision: "abcdef", }, nil } func (p testSubcontainersInfoProvider) GetMachineInfo() (*info.MachineInfo, error) { return &info.MachineInfo{ NumCores: 4, MemoryCapacity: 1024, }, nil } func (p testSubcontainersInfoProvider) SubcontainersInfo(string, *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { return []*info.ContainerInfo{ { ContainerReference: info.ContainerReference{ Name: "testcontainer", Aliases: []string{"testcontaineralias"}, }, Spec: info.ContainerSpec{ Image: "test", HasCpu: true, Cpu: info.CpuSpec{ Limit: 1000, Period: 100000, Quota: 10000, }, Memory: info.MemorySpec{ Limit: 2048, Reservation: 1024, SwapLimit: 4096, }, HasProcesses: true, Processes: info.ProcessSpec{ Limit: 100, }, CreationTime: time.Unix(1257894000, 0), Labels: map[string]string{ "foo.label": "bar", }, Envs: map[string]string{ "foo+env": "prod", }, }, Stats: []*info.ContainerStats{ { Timestamp: time.Unix(1395066363, 0), Cpu: info.CpuStats{ Usage: info.CpuUsage{ Total: 1, PerCpu: []uint64{2, 3, 4, 5}, User: 6, System: 7, }, CFS: info.CpuCFS{ Periods: 723, ThrottledPeriods: 18, ThrottledTime: 1724314000, }, Schedstat: info.CpuSchedstat{ RunTime: 53643567, RunqueueTime: 479424566378, RunPeriods: 984285, }, LoadAverage: 2, }, Memory: info.MemoryStats{ Usage: 8, MaxUsage: 8, WorkingSet: 9, ContainerData: info.MemoryStatsMemoryData{ Pgfault: 10, Pgmajfault: 11, }, HierarchicalData: info.MemoryStatsMemoryData{ Pgfault: 12, Pgmajfault: 13, }, Cache: 14, RSS: 15, MappedFile: 16, Swap: 8192, }, Network: info.NetworkStats{ InterfaceStats: info.InterfaceStats{ Name: "eth0", RxBytes: 14, RxPackets: 15, RxErrors: 16, RxDropped: 17, TxBytes: 18, TxPackets: 19, TxErrors: 20, TxDropped: 21, }, Interfaces: []info.InterfaceStats{ { Name: "eth0", RxBytes: 14, RxPackets: 15, RxErrors: 16, RxDropped: 17, TxBytes: 18, TxPackets: 19, TxErrors: 20, TxDropped: 21, }, }, Tcp: info.TcpStat{ Established: 13, SynSent: 0, SynRecv: 0, FinWait1: 0, FinWait2: 0, TimeWait: 0, Close: 0, CloseWait: 0, LastAck: 0, Listen: 3, Closing: 0, }, Tcp6: info.TcpStat{ Established: 11, SynSent: 0, SynRecv: 0, FinWait1: 0, FinWait2: 0, TimeWait: 0, Close: 0, CloseWait: 0, LastAck: 0, Listen: 3, Closing: 0, }, TcpAdvanced: info.TcpAdvancedStat{ TCPFullUndo: 2361, TCPMD5NotFound: 0, TCPDSACKRecv: 83680, TCPSackShifted: 2, TCPSackShiftFallback: 298, PFMemallocDrop: 0, EstabResets: 37, InSegs: 140370590, TCPPureAcks: 24251339, TCPDSACKOldSent: 15633, IPReversePathFilter: 0, TCPFastOpenPassiveFail: 0, InCsumErrors: 0, TCPRenoFailures: 43414, TCPMemoryPressuresChrono: 0, TCPDeferAcceptDrop: 0, TW: 10436427, TCPSpuriousRTOs: 0, TCPDSACKIgnoredNoUndo: 71885, RtoMax: 120000, ActiveOpens: 11038621, EmbryonicRsts: 0, RcvPruned: 0, TCPLossProbeRecovery: 401, TCPHPHits: 56096478, TCPPartialUndo: 3, TCPAbortOnMemory: 0, AttemptFails: 48997, RetransSegs: 462961, SyncookiesFailed: 0, OfoPruned: 0, TCPAbortOnLinger: 0, TCPAbortFailed: 0, TCPRenoReorder: 839, TCPRcvCollapsed: 0, TCPDSACKIgnoredOld: 0, TCPReqQFullDrop: 0, OutOfWindowIcmps: 0, TWKilled: 0, TCPLossProbes: 88648, TCPRenoRecoveryFail: 394, TCPFastOpenCookieReqd: 0, TCPHPAcks: 21490641, TCPSACKReneging: 0, TCPTSReorder: 3, TCPSlowStartRetrans: 290832, MaxConn: -1, SyncookiesRecv: 0, TCPSackFailures: 60, DelayedACKLocked: 90, TCPDSACKOfoSent: 1, TCPSynRetrans: 988, TCPDSACKOfoRecv: 10, TCPSACKDiscard: 0, TCPMD5Unexpected: 0, TCPSackMerged: 6, RtoMin: 200, CurrEstab: 22, TCPTimeWaitOverflow: 0, ListenOverflows: 0, DelayedACKs: 503975, TCPLossUndo: 61374, TCPOrigDataSent: 130698387, TCPBacklogDrop: 0, TCPReqQFullDoCookies: 0, TCPFastOpenPassive: 0, PAWSActive: 0, OutRsts: 91699, TCPSackRecoveryFail: 2, DelayedACKLost: 18843, TCPAbortOnData: 8, TCPMinTTLDrop: 0, PruneCalled: 0, TWRecycled: 0, ListenDrops: 0, TCPAbortOnTimeout: 0, SyncookiesSent: 0, TCPSACKReorder: 11, TCPDSACKUndo: 33, TCPMD5Failure: 0, TCPLostRetransmit: 0, TCPAbortOnClose: 7, TCPFastOpenListenOverflow: 0, OutSegs: 211580512, InErrs: 31, TCPTimeouts: 27422, TCPLossFailures: 729, TCPSackRecovery: 159, RtoAlgorithm: 1, PassiveOpens: 59, LockDroppedIcmps: 0, TCPRenoRecovery: 3519, TCPFACKReorder: 0, TCPFastRetrans: 11794, TCPRetransFail: 0, TCPMemoryPressures: 0, TCPFastOpenActive: 0, TCPFastOpenActiveFail: 0, PAWSEstab: 0, }, Udp: info.UdpStat{ Listen: 0, Dropped: 0, RxQueued: 0, TxQueued: 0, }, Udp6: info.UdpStat{ Listen: 0, Dropped: 0, RxQueued: 0, TxQueued: 0, }, }, Filesystem: []info.FsStats{ { Device: "sda1", InodesFree: 524288, Inodes: 2097152, Limit: 22, Usage: 23, ReadsCompleted: 24, ReadsMerged: 25, SectorsRead: 26, ReadTime: 27, WritesCompleted: 28, WritesMerged: 39, SectorsWritten: 40, WriteTime: 41, IoInProgress: 42, IoTime: 43, WeightedIoTime: 44, }, { Device: "sda2", InodesFree: 262144, Inodes: 2097152, Limit: 37, Usage: 38, ReadsCompleted: 39, ReadsMerged: 40, SectorsRead: 41, ReadTime: 42, WritesCompleted: 43, WritesMerged: 44, SectorsWritten: 45, WriteTime: 46, IoInProgress: 47, IoTime: 48, WeightedIoTime: 49, }, }, Accelerators: []info.AcceleratorStats{ { Make: "nvidia", Model: "tesla-p100", ID: "GPU-deadbeef-1234-5678-90ab-feedfacecafe", MemoryTotal: 20304050607, MemoryUsed: 2030405060, DutyCycle: 12, }, { Make: "nvidia", Model: "tesla-k80", ID: "GPU-deadbeef-0123-4567-89ab-feedfacecafe", MemoryTotal: 10203040506, MemoryUsed: 1020304050, DutyCycle: 6, }, }, Processes: info.ProcessStats{ ProcessCount: 1, FdCount: 5, SocketCount: 3, ThreadsCurrent: 5, ThreadsMax: 100, Ulimits: []info.UlimitSpec{ { Name: "max_open_files", SoftLimit: 16384, HardLimit: 16384, }, }, }, TaskStats: info.LoadStats{ NrSleeping: 50, NrRunning: 51, NrStopped: 52, NrUninterruptible: 53, NrIoWait: 54, }, CustomMetrics: map[string][]info.MetricVal{ "container_custom_app_metric_1": { { FloatValue: float64(1.1), Timestamp: time.Now(), Label: "testlabel_1_1_1", Labels: map[string]string{"test_label": "1_1", "test_label_2": "2_1"}, }, { FloatValue: float64(1.2), Timestamp: time.Now(), Label: "testlabel_1_1_2", Labels: map[string]string{"test_label": "1_2", "test_label_2": "2_2"}, }, }, "container_custom_app_metric_2": { { FloatValue: float64(2), Timestamp: time.Now(), Label: "testlabel2", Labels: map[string]string{"test_label": "test_value"}, }, }, "container_custom_app_metric_3": { { FloatValue: float64(3), Timestamp: time.Now(), Label: "testlabel3", Labels: map[string]string{"test_label": "test_value"}, }, }, }, }, }, }, }, nil } var ( includeRe = regexp.MustCompile(`^(?:(?:# HELP |# TYPE )?container_|cadvisor_version_info\{)`) ignoreRe = regexp.MustCompile(`^container_last_seen\{`) ) func TestPrometheusCollector(t *testing.T) { c := NewPrometheusCollector(testSubcontainersInfoProvider{}, func(container *info.ContainerInfo) map[string]string { s := DefaultContainerLabels(container) s["zone.name"] = "hello" return s }, container.AllMetrics) prometheus.MustRegister(c) defer prometheus.Unregister(c) testPrometheusCollector(t, c, "testdata/prometheus_metrics") } func testPrometheusCollector(t *testing.T, c *PrometheusCollector, metricsFile string) { rw := httptest.NewRecorder() prometheus.Handler().ServeHTTP(rw, &http.Request{}) wantMetrics, err := ioutil.ReadFile(metricsFile) if err != nil { t.Fatalf("unable to read input test file %s", metricsFile) } wantLines := strings.Split(string(wantMetrics), "\n") gotLines := strings.Split(string(rw.Body.String()), "\n") // Until the Prometheus Go client library offers better testability // (https://github.com/prometheus/client_golang/issues/58), we simply compare // verbatim text-format metrics outputs, but ignore certain metric lines // whose value depends on the current time or local circumstances. for i, want := range wantLines { if !includeRe.MatchString(want) || ignoreRe.MatchString(want) { continue } if want != gotLines[i] { t.Fatalf("unexpected metric line\nwant: %s\nhave: %s", want, gotLines[i]) } } } type erroringSubcontainersInfoProvider struct { successfulProvider testSubcontainersInfoProvider shouldFail bool } func (p *erroringSubcontainersInfoProvider) GetVersionInfo() (*info.VersionInfo, error) { if p.shouldFail { return nil, errors.New("Oops 1") } return p.successfulProvider.GetVersionInfo() } func (p *erroringSubcontainersInfoProvider) GetMachineInfo() (*info.MachineInfo, error) { if p.shouldFail { return nil, errors.New("Oops 2") } return p.successfulProvider.GetMachineInfo() } func (p *erroringSubcontainersInfoProvider) SubcontainersInfo( a string, r *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { if p.shouldFail { return []*info.ContainerInfo{}, errors.New("Oops 3") } return p.successfulProvider.SubcontainersInfo(a, r) } func TestPrometheusCollector_scrapeFailure(t *testing.T) { provider := &erroringSubcontainersInfoProvider{ successfulProvider: testSubcontainersInfoProvider{}, shouldFail: true, } c := NewPrometheusCollector(provider, func(container *info.ContainerInfo) map[string]string { s := DefaultContainerLabels(container) s["zone.name"] = "hello" return s }, container.AllMetrics) prometheus.MustRegister(c) defer prometheus.Unregister(c) testPrometheusCollector(t, c, "testdata/prometheus_metrics_failure") provider.shouldFail = false testPrometheusCollector(t, c, "testdata/prometheus_metrics") }