// RunScraper implements Target. func (t *Target) RunScraper(sampleAppender storage.SampleAppender) { defer close(t.scraperStopped) lastScrapeInterval := t.interval() log.Debugf("Starting scraper for target %v...", t) select { case <-time.After(t.offset(lastScrapeInterval)): // Continue after scraping offset. case <-t.scraperStopping: return } ticker := time.NewTicker(lastScrapeInterval) defer ticker.Stop() t.scrape(sampleAppender) // Explanation of the contraption below: // // In case t.scraperStopping has something to receive, we want to read // from that channel rather than starting a new scrape (which might take very // long). That's why the outer select has no ticker.C. Should t.scraperStopping // not have anything to receive, we go into the inner select, where ticker.C // is in the mix. for { select { case <-t.scraperStopping: return default: select { case <-t.scraperStopping: return case <-ticker.C: took := time.Since(t.status.LastScrape()) intervalStr := lastScrapeInterval.String() // On changed scrape interval the new interval becomes effective // after the next scrape. if iv := t.interval(); iv != lastScrapeInterval { ticker.Stop() ticker = time.NewTicker(iv) lastScrapeInterval = iv } targetIntervalLength.WithLabelValues(intervalStr).Observe( float64(took) / float64(time.Second), // Sub-second precision. ) if sampleAppender.NeedsThrottling() { targetSkippedScrapes.WithLabelValues(intervalStr).Inc() t.status.setLastError(errSkippedScrape) continue } t.scrape(sampleAppender) } } } }