1// Copyright 2018 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package prometheus 15 16import ( 17 "runtime" 18 "testing" 19 "time" 20 21 dto "github.com/prometheus/client_model/go" 22) 23 24func TestGoCollectorGoroutines(t *testing.T) { 25 var ( 26 c = NewGoCollector() 27 metricCh = make(chan Metric) 28 waitCh = make(chan struct{}) 29 endGoroutineCh = make(chan struct{}) 30 endCollectionCh = make(chan struct{}) 31 old = -1 32 ) 33 defer func() { 34 close(endGoroutineCh) 35 // Drain the collect channel to prevent goroutine leak. 36 for { 37 select { 38 case <-metricCh: 39 case <-endCollectionCh: 40 return 41 } 42 } 43 }() 44 45 go func() { 46 c.Collect(metricCh) 47 go func(c <-chan struct{}) { 48 <-c 49 }(endGoroutineCh) 50 <-waitCh 51 c.Collect(metricCh) 52 close(endCollectionCh) 53 }() 54 55 for { 56 select { 57 case m := <-metricCh: 58 // m can be Gauge or Counter, 59 // currently just test the go_goroutines Gauge 60 // and ignore others. 61 if m.Desc().fqName != "go_goroutines" { 62 continue 63 } 64 pb := &dto.Metric{} 65 m.Write(pb) 66 if pb.GetGauge() == nil { 67 continue 68 } 69 70 if old == -1 { 71 old = int(pb.GetGauge().GetValue()) 72 close(waitCh) 73 continue 74 } 75 76 if diff := int(pb.GetGauge().GetValue()) - old; diff != 1 { 77 // TODO: This is flaky in highly concurrent situations. 78 t.Errorf("want 1 new goroutine, got %d", diff) 79 } 80 case <-time.After(1 * time.Second): 81 t.Fatalf("expected collect timed out") 82 } 83 break 84 } 85} 86 87func TestGoCollectorGC(t *testing.T) { 88 var ( 89 c = NewGoCollector() 90 metricCh = make(chan Metric) 91 waitCh = make(chan struct{}) 92 endCollectionCh = make(chan struct{}) 93 oldGC uint64 94 oldPause float64 95 ) 96 97 go func() { 98 c.Collect(metricCh) 99 // force GC 100 runtime.GC() 101 <-waitCh 102 c.Collect(metricCh) 103 close(endCollectionCh) 104 }() 105 106 defer func() { 107 // Drain the collect channel to prevent goroutine leak. 108 for { 109 select { 110 case <-metricCh: 111 case <-endCollectionCh: 112 return 113 } 114 } 115 }() 116 117 first := true 118 for { 119 select { 120 case metric := <-metricCh: 121 pb := &dto.Metric{} 122 metric.Write(pb) 123 if pb.GetSummary() == nil { 124 continue 125 } 126 if len(pb.GetSummary().Quantile) != 5 { 127 t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile)) 128 } 129 for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} { 130 if *pb.GetSummary().Quantile[idx].Quantile != want { 131 t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want) 132 } 133 } 134 if first { 135 first = false 136 oldGC = *pb.GetSummary().SampleCount 137 oldPause = *pb.GetSummary().SampleSum 138 close(waitCh) 139 continue 140 } 141 if diff := *pb.GetSummary().SampleCount - oldGC; diff < 1 { 142 t.Errorf("want at least 1 new garbage collection run, got %d", diff) 143 } 144 if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 { 145 t.Errorf("want an increase in pause time, got a change of %f", diff) 146 } 147 case <-time.After(1 * time.Second): 148 t.Fatalf("expected collect timed out") 149 } 150 break 151 } 152} 153 154func TestGoCollectorMemStats(t *testing.T) { 155 var ( 156 c = NewGoCollector().(*goCollector) 157 got uint64 158 ) 159 160 checkCollect := func(want uint64) { 161 metricCh := make(chan Metric) 162 endCh := make(chan struct{}) 163 164 go func() { 165 c.Collect(metricCh) 166 close(endCh) 167 }() 168 Collect: 169 for { 170 select { 171 case metric := <-metricCh: 172 if metric.Desc().fqName != "go_memstats_alloc_bytes" { 173 continue Collect 174 } 175 pb := &dto.Metric{} 176 metric.Write(pb) 177 got = uint64(pb.GetGauge().GetValue()) 178 case <-endCh: 179 break Collect 180 } 181 } 182 if want != got { 183 t.Errorf("unexpected value of go_memstats_alloc_bytes, want %d, got %d", want, got) 184 } 185 } 186 187 // Speed up the timing to make the test faster. 188 c.msMaxWait = 5 * time.Millisecond 189 c.msMaxAge = 50 * time.Millisecond 190 191 // Scenario 1: msRead responds slowly, no previous memstats available, 192 // msRead is executed anyway. 193 c.msRead = func(ms *runtime.MemStats) { 194 time.Sleep(20 * time.Millisecond) 195 ms.Alloc = 1 196 } 197 checkCollect(1) 198 // Now msLast is set. 199 c.msMtx.Lock() 200 if want, got := uint64(1), c.msLast.Alloc; want != got { 201 t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got) 202 } 203 c.msMtx.Unlock() 204 205 // Scenario 2: msRead responds fast, previous memstats available, new 206 // value collected. 207 c.msRead = func(ms *runtime.MemStats) { 208 ms.Alloc = 2 209 } 210 checkCollect(2) 211 // msLast is set, too. 212 c.msMtx.Lock() 213 if want, got := uint64(2), c.msLast.Alloc; want != got { 214 t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got) 215 } 216 c.msMtx.Unlock() 217 218 // Scenario 3: msRead responds slowly, previous memstats available, old 219 // value collected. 220 c.msRead = func(ms *runtime.MemStats) { 221 time.Sleep(20 * time.Millisecond) 222 ms.Alloc = 3 223 } 224 checkCollect(2) 225 // After waiting, new value is still set in msLast. 226 time.Sleep(80 * time.Millisecond) 227 c.msMtx.Lock() 228 if want, got := uint64(3), c.msLast.Alloc; want != got { 229 t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got) 230 } 231 c.msMtx.Unlock() 232 233 // Scenario 4: msRead responds slowly, previous memstats is too old, new 234 // value collected. 235 c.msRead = func(ms *runtime.MemStats) { 236 time.Sleep(20 * time.Millisecond) 237 ms.Alloc = 4 238 } 239 checkCollect(4) 240 c.msMtx.Lock() 241 if want, got := uint64(4), c.msLast.Alloc; want != got { 242 t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got) 243 } 244 c.msMtx.Unlock() 245} 246