func parseTime(s string) (model.Time, error) { if t, err := strconv.ParseFloat(s, 64); err == nil { ts := int64(t * float64(time.Second)) return model.TimeFromUnixNano(ts), nil } if t, err := time.Parse(time.RFC3339Nano, s); err == nil { return model.TimeFromUnixNano(t.UnixNano()), nil } return 0, fmt.Errorf("cannot parse %q to a valid timestamp", s) }
func TestTargetScraperScrapeOK(t *testing.T) { server := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", `text/plain; version=0.0.4`) w.Write([]byte("metric_a 1\nmetric_b 2\n")) }), ) defer server.Close() serverURL, err := url.Parse(server.URL) if err != nil { panic(err) } ts := &targetScraper{ Target: &Target{ labels: model.LabelSet{ model.SchemeLabel: model.LabelValue(serverURL.Scheme), model.AddressLabel: model.LabelValue(serverURL.Host), }, }, client: http.DefaultClient, } now := time.Now() samples, err := ts.scrape(context.Background(), now) if err != nil { t.Fatalf("Unexpected scrape error: %s", err) } expectedSamples := model.Samples{ { Metric: model.Metric{"__name__": "metric_a"}, Timestamp: model.TimeFromUnixNano(now.UnixNano()), Value: 1, }, { Metric: model.Metric{"__name__": "metric_b"}, Timestamp: model.TimeFromUnixNano(now.UnixNano()), Value: 2, }, } sort.Sort(expectedSamples) sort.Sort(samples) if !reflect.DeepEqual(samples, expectedSamples) { t.Errorf("Scraped samples did not match served metrics") t.Errorf("Expected: %v", expectedSamples) t.Fatalf("Got: %v", samples) } }
func extractSummary(o *DecodeOptions, f *dto.MetricFamily) model.Vector { samples := make(model.Vector, 0, len(f.Metric)) for _, m := range f.Metric { if m.Summary == nil { continue } timestamp := o.Timestamp if m.TimestampMs != nil { timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) } for _, q := range m.Summary.Quantile { lset := make(model.LabelSet, len(m.Label)+2) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } // BUG(matt): Update other names to "quantile". lset[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile())) lset[model.MetricNameLabel] = model.LabelValue(f.GetName()) samples = append(samples, &model.Sample{ Metric: model.Metric(lset), Value: model.SampleValue(q.GetValue()), Timestamp: timestamp, }) } if m.Summary.SampleSum != nil { lset := make(model.LabelSet, len(m.Label)+1) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") samples = append(samples, &model.Sample{ Metric: model.Metric(lset), Value: model.SampleValue(m.Summary.GetSampleSum()), Timestamp: timestamp, }) } if m.Summary.SampleCount != nil { lset := make(model.LabelSet, len(m.Label)+1) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") samples = append(samples, &model.Sample{ Metric: model.Metric(lset), Value: model.SampleValue(m.Summary.GetSampleCount()), Timestamp: timestamp, }) } } return samples }
func extractUntyped(o *DecodeOptions, f *dto.MetricFamily) model.Vector { samples := make(model.Vector, 0, len(f.Metric)) for _, m := range f.Metric { if m.Untyped == nil { continue } lset := make(model.LabelSet, len(m.Label)+1) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } lset[model.MetricNameLabel] = model.LabelValue(f.GetName()) smpl := &model.Sample{ Metric: model.Metric(lset), Value: model.SampleValue(m.Untyped.GetValue()), } if m.TimestampMs != nil { smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) } else { smpl.Timestamp = o.Timestamp } samples = append(samples, smpl) } return samples }
func recordScrapeHealth( sampleAppender storage.SampleAppender, timestamp time.Time, baseLabels model.LabelSet, health TargetHealth, scrapeDuration time.Duration, ) { healthMetric := make(model.Metric, len(baseLabels)+1) durationMetric := make(model.Metric, len(baseLabels)+1) healthMetric[model.MetricNameLabel] = scrapeHealthMetricName durationMetric[model.MetricNameLabel] = scrapeDurationMetricName for ln, lv := range baseLabels { healthMetric[ln] = lv durationMetric[ln] = lv } ts := model.TimeFromUnixNano(timestamp.UnixNano()) healthSample := &model.Sample{ Metric: healthMetric, Timestamp: ts, Value: health.value(), } durationSample := &model.Sample{ Metric: durationMetric, Timestamp: ts, Value: model.SampleValue(float64(scrapeDuration) / float64(time.Second)), } sampleAppender.Append(healthSample) sampleAppender.Append(durationSample) }
func (sl *scrapeLoop) report(start time.Time, duration time.Duration, err error) { sl.scraper.report(start, duration, err) ts := model.TimeFromUnixNano(start.UnixNano()) var health model.SampleValue if err == nil { health = 1 } healthSample := &model.Sample{ Metric: model.Metric{ model.MetricNameLabel: scrapeHealthMetricName, }, Timestamp: ts, Value: health, } durationSample := &model.Sample{ Metric: model.Metric{ model.MetricNameLabel: scrapeDurationMetricName, }, Timestamp: ts, Value: model.SampleValue(float64(duration) / float64(time.Second)), } sl.reportAppender.Append(healthSample) sl.reportAppender.Append(durationSample) }
func (t *Target) report(app storage.SampleAppender, start time.Time, duration time.Duration, err error) { t.status.setLastScrape(start) t.status.setLastError(err) ts := model.TimeFromUnixNano(start.UnixNano()) var health model.SampleValue if err == nil { health = 1 } healthSample := &model.Sample{ Metric: model.Metric{ model.MetricNameLabel: scrapeHealthMetricName, }, Timestamp: ts, Value: health, } durationSample := &model.Sample{ Metric: model.Metric{ model.MetricNameLabel: scrapeDurationMetricName, }, Timestamp: ts, Value: model.SampleValue(float64(duration) / float64(time.Second)), } app = t.wrapReportingAppender(app) app.Append(healthSample) app.Append(durationSample) }
func (sl *scrapeLoop) report(start time.Time, duration time.Duration, err error) { sl.scraper.report(start, duration, err) ts := model.TimeFromUnixNano(start.UnixNano()) var health model.SampleValue if err == nil { health = 1 } healthSample := &model.Sample{ Metric: model.Metric{ model.MetricNameLabel: scrapeHealthMetricName, }, Timestamp: ts, Value: health, } durationSample := &model.Sample{ Metric: model.Metric{ model.MetricNameLabel: scrapeDurationMetricName, }, Timestamp: ts, Value: model.SampleValue(float64(duration) / float64(time.Second)), } if err := sl.reportAppender.Append(healthSample); err != nil { log.With("sample", healthSample).With("error", err).Warn("Scrape health sample discarded") } if err := sl.reportAppender.Append(durationSample); err != nil { log.With("sample", durationSample).With("error", err).Warn("Scrape duration sample discarded") } }
func parseTimestampOrNow(t string, now model.Time) (model.Time, error) { if t == "" { return now, nil } tFloat, err := strconv.ParseFloat(t, 64) if err != nil { return 0, err } return model.TimeFromUnixNano(int64(tFloat * float64(time.Second/time.Nanosecond))), nil }
func TestParseTime(t *testing.T) { ts, err := time.Parse(time.RFC3339Nano, "2015-06-03T13:21:58.555Z") if err != nil { panic(err) } var tests = []struct { input string fail bool result time.Time }{ { input: "", fail: true, }, { input: "abc", fail: true, }, { input: "30s", fail: true, }, { input: "123", result: time.Unix(123, 0), }, { input: "123.123", result: time.Unix(123, 123000000), }, { input: "123.123", result: time.Unix(123, 123000000), }, { input: "2015-06-03T13:21:58.555Z", result: ts, }, { input: "2015-06-03T14:21:58.555+01:00", result: ts, }, } for _, test := range tests { ts, err := parseTime(test.input) if err != nil && !test.fail { t.Errorf("Unexpected error for %q: %s", test.input, err) continue } if err == nil && test.fail { t.Errorf("Expected error for %q but got none", test.input) continue } res := model.TimeFromUnixNano(test.result.UnixNano()) if !test.fail && ts != res { t.Errorf("Expected time %v for input %q but got %v", res, test.input, ts) } } }
func (s *targetScraper) scrape(ctx context.Context, ts time.Time) (model.Samples, error) { req, err := http.NewRequest("GET", s.URL().String(), nil) if err != nil { return nil, err } req.Header.Add("Accept", acceptHeader) resp, err := ctxhttp.Do(ctx, s.client, req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("server returned HTTP status %s", resp.Status) } var ( allSamples = make(model.Samples, 0, 200) decSamples = make(model.Vector, 0, 50) ) sdec := expfmt.SampleDecoder{ Dec: expfmt.NewDecoder(resp.Body, expfmt.ResponseFormat(resp.Header)), Opts: &expfmt.DecodeOptions{ Timestamp: model.TimeFromUnixNano(ts.UnixNano()), }, } for { if err = sdec.Decode(&decSamples); err != nil { break } allSamples = append(allSamples, decSamples...) decSamples = decSamples[:0] } if err == io.EOF { // Set err to nil since it is used in the scrape health recording. err = nil } return allSamples, err }
func TestParseTimestampOrNow(t *testing.T) { ts, err := parseTimestampOrNow("", testNow()) if err != nil { t.Fatalf("err = %s; want nil", err) } if !testNow().Equal(ts) { t.Fatalf("ts = %v; want ts = %v", ts, testNow) } ts, err = parseTimestampOrNow("1426956073.12345", testNow()) if err != nil { t.Fatalf("err = %s; want nil", err) } expTS := model.TimeFromUnixNano(1426956073123000000) if !ts.Equal(expTS) { t.Fatalf("ts = %v; want %v", ts, expTS) } _, err = parseTimestampOrNow("123.45foo", testNow()) if err == nil { t.Fatalf("err = nil; want %s", err) } }
func verifyStorage(t testing.TB, s *memorySeriesStorage, samples model.Samples, maxAge time.Duration) bool { s.WaitForIndexing() result := true for _, i := range rand.Perm(len(samples)) { sample := samples[i] if sample.Timestamp.Before(model.TimeFromUnixNano(time.Now().Add(-maxAge).UnixNano())) { continue // TODO: Once we have a guaranteed cutoff at the // retention period, we can verify here that no results // are returned. } fp, err := s.mapper.mapFP(sample.Metric.FastFingerprint(), sample.Metric) if err != nil { t.Fatal(err) } p := s.NewPreloader() p.PreloadRange(fp, sample.Timestamp, sample.Timestamp, time.Hour) found := s.NewIterator(fp).ValueAtTime(sample.Timestamp) if len(found) != 1 { t.Errorf("Sample %#v: Expected exactly one value, found %d.", sample, len(found)) result = false p.Close() continue } want := sample.Value got := found[0].Value if want != got || sample.Timestamp != found[0].Timestamp { t.Errorf( "Value (or timestamp) mismatch, want %f (at time %v), got %f (at time %v).", want, sample.Timestamp, got, found[0].Timestamp, ) result = false } p.Close() } return result }
func (t *Target) scrape(appender storage.SampleAppender) (err error) { start := time.Now() baseLabels := t.BaseLabels() defer func(appender storage.SampleAppender) { t.status.setLastError(err) recordScrapeHealth(appender, start, baseLabels, t.status.Health(), time.Since(start)) }(appender) t.RLock() // The relabelAppender has to be inside the label-modifying appenders // so the relabeling rules are applied to the correct label set. if len(t.metricRelabelConfigs) > 0 { appender = relabelAppender{ app: appender, relabelings: t.metricRelabelConfigs, } } if t.honorLabels { appender = honorLabelsAppender{ app: appender, labels: baseLabels, } } else { appender = ruleLabelsAppender{ app: appender, labels: baseLabels, } } httpClient := t.httpClient t.RUnlock() req, err := http.NewRequest("GET", t.URL().String(), nil) if err != nil { return err } req.Header.Add("Accept", acceptHeader) resp, err := httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("server returned HTTP status %s", resp.Status) } dec := expfmt.NewDecoder(resp.Body, expfmt.ResponseFormat(resp.Header)) sdec := expfmt.SampleDecoder{ Dec: dec, Opts: &expfmt.DecodeOptions{ Timestamp: model.TimeFromUnixNano(start.UnixNano()), }, } t.ingestedSamples = make(chan model.Vector, ingestedSamplesCap) go func() { for { // TODO(fabxc): Change the SampleAppender interface to return an error // so we can proceed based on the status and don't leak goroutines trying // to append a single sample after dropping all the other ones. // // This will also allow use to reuse this vector and save allocations. var samples model.Vector if err = sdec.Decode(&samples); err != nil { break } if err = t.ingest(samples); err != nil { break } } close(t.ingestedSamples) }() for samples := range t.ingestedSamples { for _, s := range samples { appender.Append(s) } } if err == io.EOF { return nil } return err }
func (t *Target) scrape(sampleAppender storage.SampleAppender) (err error) { start := time.Now() baseLabels := t.BaseLabels() t.RLock() var ( honorLabels = t.honorLabels httpClient = t.httpClient metricRelabelConfigs = t.metricRelabelConfigs ) t.RUnlock() defer func() { t.status.setLastError(err) recordScrapeHealth(sampleAppender, start, baseLabels, t.status.Health(), time.Since(start)) }() req, err := http.NewRequest("GET", t.URL().String(), nil) if err != nil { panic(err) } req.Header.Add("Accept", acceptHeader) resp, err := httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("server returned HTTP status %s", resp.Status) } dec, err := expfmt.NewDecoder(resp.Body, resp.Header) if err != nil { return err } defer resp.Body.Close() sdec := expfmt.SampleDecoder{ Dec: dec, Opts: &expfmt.DecodeOptions{ Timestamp: model.TimeFromUnixNano(start.UnixNano()), }, } t.ingestedSamples = make(chan model.Vector, ingestedSamplesCap) go func() { for { // TODO(fabxc): Changex the SampleAppender interface to return an error // so we can proceed based on the status and don't leak goroutines trying // to append a single sample after dropping all the other ones. // // This will also allow use to reuse this vector and save allocations. var samples model.Vector if err = sdec.Decode(&samples); err != nil { break } if err = t.ingest(samples); err != nil { break } } close(t.ingestedSamples) }() for samples := range t.ingestedSamples { for _, s := range samples { if honorLabels { // Merge the metric with the baseLabels for labels not already set in the // metric. This also considers labels explicitly set to the empty string. for ln, lv := range baseLabels { if _, ok := s.Metric[ln]; !ok { s.Metric[ln] = lv } } } else { // Merge the ingested metric with the base label set. On a collision the // value of the label is stored in a label prefixed with the exported prefix. for ln, lv := range baseLabels { if v, ok := s.Metric[ln]; ok && v != "" { s.Metric[model.ExportedLabelPrefix+ln] = v } s.Metric[ln] = lv } } // Avoid the copy in Relabel if there are no configs. if len(metricRelabelConfigs) > 0 { labels, err := Relabel(model.LabelSet(s.Metric), metricRelabelConfigs...) if err != nil { log.Errorf("Error while relabeling metric %s of instance %s: %s", s.Metric, req.URL, err) continue } // Check if the timeseries was dropped. if labels == nil { continue } s.Metric = model.Metric(labels) } sampleAppender.Append(s) } } if err == io.EOF { return nil } return err }
// NewTemplateExpander returns a template expander ready to use. func NewTemplateExpander(text string, name string, data interface{}, timestamp model.Time, queryEngine *promql.Engine, pathPrefix string) *Expander { return &Expander{ text: text, name: name, data: data, funcMap: text_template.FuncMap{ "query": func(q string) (queryResult, error) { return query(q, timestamp, queryEngine) }, "first": func(v queryResult) (*sample, error) { if len(v) > 0 { return v[0], nil } return nil, errors.New("first() called on vector with no elements") }, "label": func(label string, s *sample) string { return s.Labels[label] }, "value": func(s *sample) float64 { return s.Value }, "strvalue": func(s *sample) string { return s.Labels["__value__"] }, "args": func(args ...interface{}) map[string]interface{} { result := make(map[string]interface{}) for i, a := range args { result[fmt.Sprintf("arg%d", i)] = a } return result }, "reReplaceAll": func(pattern, repl, text string) string { re := regexp.MustCompile(pattern) return re.ReplaceAllString(text, repl) }, "safeHtml": func(text string) html_template.HTML { return html_template.HTML(text) }, "match": regexp.MatchString, "title": strings.Title, "graphLink": strutil.GraphLinkForExpression, "tableLink": strutil.TableLinkForExpression, "sortByLabel": func(label string, v queryResult) queryResult { sorter := queryResultByLabelSorter{v[:], label} sort.Stable(sorter) return v }, "humanize": func(v float64) string { if v == 0 || math.IsNaN(v) || math.IsInf(v, 0) { return fmt.Sprintf("%.4g", v) } if math.Abs(v) >= 1 { prefix := "" for _, p := range []string{"k", "M", "G", "T", "P", "E", "Z", "Y"} { if math.Abs(v) < 1000 { break } prefix = p v /= 1000 } return fmt.Sprintf("%.4g%s", v, prefix) } prefix := "" for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} { if math.Abs(v) >= 1 { break } prefix = p v *= 1000 } return fmt.Sprintf("%.4g%s", v, prefix) }, "humanize1024": func(v float64) string { if math.Abs(v) <= 1 || math.IsNaN(v) || math.IsInf(v, 0) { return fmt.Sprintf("%.4g", v) } prefix := "" for _, p := range []string{"ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"} { if math.Abs(v) < 1024 { break } prefix = p v /= 1024 } return fmt.Sprintf("%.4g%s", v, prefix) }, "humanizeDuration": func(v float64) string { if math.IsNaN(v) || math.IsInf(v, 0) { return fmt.Sprintf("%.4g", v) } if v == 0 { return fmt.Sprintf("%.4gs", v) } if math.Abs(v) >= 1 { sign := "" if v < 0 { sign = "-" v = -v } seconds := int64(v) % 60 minutes := (int64(v) / 60) % 60 hours := (int64(v) / 60 / 60) % 24 days := (int64(v) / 60 / 60 / 24) // For days to minutes, we display seconds as an integer. if days != 0 { return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds) } if hours != 0 { return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds) } if minutes != 0 { return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds) } // For seconds, we display 4 significant digts. return fmt.Sprintf("%s%.4gs", sign, v) } prefix := "" for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} { if math.Abs(v) >= 1 { break } prefix = p v *= 1000 } return fmt.Sprintf("%.4g%ss", v, prefix) }, "humanizeTimestamp": func(v float64) string { if math.IsNaN(v) || math.IsInf(v, 0) { return fmt.Sprintf("%.4g", v) } t := model.TimeFromUnixNano(int64(v * 1e9)).Time().UTC() return fmt.Sprint(t) }, "pathPrefix": func() string { return pathPrefix }, }, } }
func (t *Target) scrape(appender storage.SampleAppender) (err error) { start := time.Now() baseLabels := t.BaseLabels() defer func(appender storage.SampleAppender) { t.status.setLastError(err) recordScrapeHealth(appender, start, baseLabels, t.status.Health(), time.Since(start)) }(appender) t.RLock() // The relabelAppender has to be inside the label-modifying appenders // so the relabeling rules are applied to the correct label set. if len(t.metricRelabelConfigs) > 0 { appender = relabelAppender{ SampleAppender: appender, relabelings: t.metricRelabelConfigs, } } if t.honorLabels { appender = honorLabelsAppender{ SampleAppender: appender, labels: baseLabels, } } else { appender = ruleLabelsAppender{ SampleAppender: appender, labels: baseLabels, } } httpClient := t.httpClient t.RUnlock() req, err := http.NewRequest("GET", t.URL().String(), nil) if err != nil { return err } req.Header.Add("Accept", acceptHeader) resp, err := httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("server returned HTTP status %s", resp.Status) } dec := expfmt.NewDecoder(resp.Body, expfmt.ResponseFormat(resp.Header)) sdec := expfmt.SampleDecoder{ Dec: dec, Opts: &expfmt.DecodeOptions{ Timestamp: model.TimeFromUnixNano(start.UnixNano()), }, } var ( samples model.Vector numOutOfOrder int logger = log.With("target", t.InstanceIdentifier()) ) for { if err = sdec.Decode(&samples); err != nil { break } for _, s := range samples { err := appender.Append(s) if err != nil { if err == local.ErrOutOfOrderSample { numOutOfOrder++ } else { logger.With("sample", s).Warnf("Error inserting sample: %s", err) } } } } if numOutOfOrder > 0 { logger.With("numDropped", numOutOfOrder).Warn("Error on ingesting out-of-order samples") } if err == io.EOF { return nil } return err }
func (t *Target) scrape(appender storage.SampleAppender) error { var ( err error start = time.Now() ) defer func(appender storage.SampleAppender) { t.report(appender, start, time.Since(start), err) }(appender) t.RLock() appender = t.wrapAppender(appender) client := t.httpClient t.RUnlock() req, err := http.NewRequest("GET", t.URL().String(), nil) if err != nil { return err } req.Header.Add("Accept", acceptHeader) ctx, _ := context.WithTimeout(context.Background(), t.timeout()) resp, err := ctxhttp.Do(ctx, client, req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("server returned HTTP status %s", resp.Status) } dec := expfmt.NewDecoder(resp.Body, expfmt.ResponseFormat(resp.Header)) sdec := expfmt.SampleDecoder{ Dec: dec, Opts: &expfmt.DecodeOptions{ Timestamp: model.TimeFromUnixNano(start.UnixNano()), }, } var ( samples model.Vector numOutOfOrder int logger = log.With("target", t.InstanceIdentifier()) ) for { if err = sdec.Decode(&samples); err != nil { break } for _, s := range samples { err := appender.Append(s) if err != nil { if err == local.ErrOutOfOrderSample { numOutOfOrder++ } else { logger.With("sample", s).Warnf("Error inserting sample: %s", err) } } } } if numOutOfOrder > 0 { logger.With("numDropped", numOutOfOrder).Warn("Error on ingesting out-of-order samples") } if err == io.EOF { // Set err to nil since it is used in the scrape health recording. err = nil } return err }
func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector { samples := make(model.Vector, 0, len(f.Metric)) for _, m := range f.Metric { if m.Histogram == nil { continue } timestamp := o.Timestamp if m.TimestampMs != nil { timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) } infSeen := false for _, q := range m.Histogram.Bucket { lset := make(model.LabelSet, len(m.Label)+2) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } lset[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound())) lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") if math.IsInf(q.GetUpperBound(), +1) { infSeen = true } samples = append(samples, &model.Sample{ Metric: model.Metric(lset), Value: model.SampleValue(q.GetCumulativeCount()), Timestamp: timestamp, }) } if m.Histogram.SampleSum != nil { lset := make(model.LabelSet, len(m.Label)+1) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") samples = append(samples, &model.Sample{ Metric: model.Metric(lset), Value: model.SampleValue(m.Histogram.GetSampleSum()), Timestamp: timestamp, }) } if m.Histogram.SampleCount != nil { lset := make(model.LabelSet, len(m.Label)+1) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") count := &model.Sample{ Metric: model.Metric(lset), Value: model.SampleValue(m.Histogram.GetSampleCount()), Timestamp: timestamp, } samples = append(samples, count) if !infSeen { // Append a infinity bucket sample. lset := make(model.LabelSet, len(m.Label)+2) for _, p := range m.Label { lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } lset[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf") lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") samples = append(samples, &model.Sample{ Metric: model.Metric(lset), Value: count.Value, Timestamp: timestamp, }) } } } return samples }
// Returns collected metrics and the next collection time of the collector func (collector *PrometheusCollector) Collect(metrics map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) { currentTime := time.Now() nextCollectionTime := currentTime.Add(time.Duration(collector.pollingFrequency)) uri := collector.configFile.Endpoint.URL response, err := collector.httpClient.Get(uri) if err != nil { return nextCollectionTime, nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nextCollectionTime, nil, fmt.Errorf("server returned HTTP status %s", response.Status) } sdec := expfmt.SampleDecoder{ Dec: expfmt.NewDecoder(response.Body, expfmt.ResponseFormat(response.Header)), Opts: &expfmt.DecodeOptions{ Timestamp: model.TimeFromUnixNano(currentTime.UnixNano()), }, } var ( // 50 is chosen as a reasonable guesstimate at a number of metrics we can // expect from virtually any endpoint to try to save allocations. decSamples = make(model.Vector, 0, 50) newMetrics = make(map[string][]v1.MetricVal) ) for { if err = sdec.Decode(&decSamples); err != nil { break } for _, sample := range decSamples { metName := string(sample.Metric[model.MetricNameLabel]) if len(metName) == 0 { continue } // If metrics to collect is specified, skip any metrics not in the list to collect. if _, ok := collector.metricsSet[metName]; collector.metricsSet != nil && !ok { continue } // TODO Handle multiple labels nicer. Prometheus metrics can have multiple // labels, cadvisor only accepts a single string for the metric label. label := prometheusLabelSetToCadvisorLabel(sample.Metric) metric := v1.MetricVal{ FloatValue: float64(sample.Value), Timestamp: sample.Timestamp.Time(), Label: label, } newMetrics[metName] = append(newMetrics[metName], metric) if len(newMetrics) > collector.metricCountLimit { return nextCollectionTime, nil, fmt.Errorf("too many metrics to collect") } } decSamples = decSamples[:0] } if err != nil && err != io.EOF { return nextCollectionTime, nil, err } for key, val := range newMetrics { metrics[key] = append(metrics[key], val...) } return nextCollectionTime, metrics, nil }