Repository: prometheus/statsd_exporter
Pull request #281 (2020-01-02 17:51:22)

Add random replacement mapper cache 
Add a simple random replacement cache based on a map.

Add command line flag to choose mapper cache type, defaulting to existing lru cache.

In the case that all entries fit into the cache, this new cache is faster as it uses a read lock for gets and also has less bookkeeping than the lru cache.

In the case that all entries do not fit into cache, the random replacement cache can avoid thrashing the cache. In some cases, the lru cache will have a very low hit ratio as it is constantly evicting entries.  Our use case is a huge number of unique metric names in "old" statsd format that we map to relatively few Prometheus metrics.

Note: I realize this is probably a much larger change than we may want all at once, but wanted to get feedback on the approach, etc. 

Benchmark results:
```
go test -bench='.*Cache.*' -benchtime=10s -benchmem ./pkg/mapper/

pkg: github.com/prometheus/statsd_exporter/pkg/mapper
BenchmarkGlob10RulesCached/lru-12                           	76762423	       155 ns/op	      48 B/op	       2 allocs/op
BenchmarkGlob10RulesCached/random-12                        	153020101	        78.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkRegex10RulesAverageCached/lru-12                   	78939664	       150 ns/op	      48 B/op	       2 allocs/op
BenchmarkRegex10RulesAverageCached/random-12                	153228613	        78.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkGlob100RulesCached/lru-12                          	78150949	       153 ns/op	      48 B/op	       2 allocs/op
BenchmarkGlob100RulesCached/random-12                       	151522714	        78.2 ns/op	       0 B/op	       0 allocs/op
BenchmarkGlob100RulesMultipleCapturesCached/lru-12          	76434423	       158 ns/op	      64 B/op	       2 allocs/op
BenchmarkGlob100RulesMultipleCapturesCached/random-12       	100000000	       105 ns/op	      48 B/op	       1 allocs/op
BenchmarkRegex100RulesMultipleCapturesWorstCached/lru-12    	75379135	       159 ns/op	      64 B/op	       2 allocs/op
BenchmarkRegex100RulesMultipleCapturesWorstCached/random-12 	100000000	       109 ns/op	      48 B/op	       1 allocs/op
BenchmarkGlob100RulesCached100Metrics/lru-12                	  643710	     18510 ns/op	    6240 B/op	     200 allocs/op
BenchmarkGlob100RulesCached100Metrics/random-12             	 1000000	     10976 ns/op	    4320 B/op	      90 allocs/op
BenchmarkGlob100RulesCached100MetricsSmallCache/lru-12      	   76018	    167613 ns/op	   28502 B/op	    1000 allocs/op
BenchmarkGlob100RulesCached100MetricsSmallCache/random-12   	  128126	     96533 ns/op	   13483 B/op	     446 allocs/op
BenchmarkGlob100RulesCached100MetricsRandomSmallCache/lru-12         	   10000	   1154307 ns/op	  183750 B/op	    6361 allocs/op
BenchmarkGlob100RulesCached100MetricsRandomSmallCache/random-12      	   16196	    731579 ns/op	  108832 B/op	    3478 allocs/op
PASS
ok  	github.com/prometheus/statsd_exporter/pkg/mapper	224.196s
```

glightfoot: If memory resources are limited for the statsd exporter, the LRU cache will provide a higher hit-rate as it's keeping the hottest mappings. However, with high throughput and lots of metrics, it can become a bottleneck due to the write lock needed for all operations. 

If memory resources are not limited and throughput is very high, the random replacement cache will outperform the LRU cache if the size is big enough to hold all the mappings since a write lock is only needed when adding new mappings. 
matthiasr: thank you for the explanation! it makes sense to have both options then.
bakins: @matthiasr I'll fix the conflicts.  I'm okay to break up the packages.  Are we worried about compatibility for others who may have imported the mapper package?  I could add a shim to ensure the old calls in mapper worked by calling into mapper/cache or something like that.
matthiasr: No, I'm not very worried about it. We have versions, and as long as the
upgrade path is clear and easy I would rather have a clean API

On Sat, 11 Jan 2020, 02:07 Brian Akins, <notifications@github.com> wrote:

> @matthiasr <https://github.com/matthiasr> I'll fix the conflicts. I'm
> okay to break up the packages. Are we worried about compatibility for
> others who may have imported the mapper package? I could add a shim to
> ensure the old calls in mapper worked by calling into mapper/cache or
> something like that.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/prometheus/statsd_exporter/pull/281?email_source=notifications&email_token=AABAEBSSITN4LVELL3ORERTQ5ELWTA5CNFSM4KCE5R62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIVUXWQ#issuecomment-573262810>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AABAEBWDJ6EPHBEPEBXB643Q5ELWTANCNFSM4KCE5R6Q>
> .
>

matthiasr: @bakins are you still willing to work on this? If the desire to break out caching from mapping is holding this up, I'm also willing to merge this as is (after resolving the conflicts) and defer the refactoring.
bakins: @matthiasr I'm okay either way. I'll fix the conflicts and then we can decide.  Whichever way is easier for you.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 77c7247..d9c4e19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,68 +1,4 @@
-## 0.15.0 / unreleased
-
-* [ENHANCEMENT] Allow setting granularity for summary metrics ([#290](https://github.com/prometheus/statsd_exporter/pull/290))
-
-To facilitate the expanded settings for summaries, the configuration format changes from
-
-```yaml
-mappings:
-- match: …
-  timer_type: summary
-  quantiles:
-    - quantile: 0.99
-      error: 0.001
-    - quantile: 0.95
-      error: 0.01
-  …
-```
-
-to
-
-```yaml
-mappings:
-- match: …
-  timer_type: summary
-  summary_options:
-    quantiles:
-      - quantile: 0.99
-        error: 0.001
-      - quantile: 0.95
-        error: 0.01
-      …
-    max_summary_age: 30s
-    summary_age_buckets: 3
-    stream_buffer_size: 1000
-  …
-```
-
-For consistency, the format for histogram buckets also changes from
-
-```yaml
-mappings:
-- match: …
-  timer_type: histogram
-  buckets: [ 0.01, 0.025, 0.05, 0.1 ]
-```
-
-to
-
-```yaml
-mappings:
-- match: …
-  timer_type: histogram
-  histogram_options:
-    buckets: [ 0.01, 0.025, 0.05, 0.1 ]
-```
-
-Transitionally, the old format will still work but is *deprecated*. The new
-settings are optional.
-
-For users of the [mapper](https://pkg.go.dev/github.com/prometheus/statsd_exporter/pkg/mapper?tab=doc)
-as a library, this is a breaking change. To adjust your code, replace
-`mapping.Buckets` with `mapping.HistogramOptions.Buckets` and
-`mapping.Quantiles` with `mapping.SummaryOptions.Quantiles`.
-
-## 0.14.1 / 2020-01-13
+## 0.14.1 / 2010-01-13
 
 * [BUGFIX] Mapper cache poisoning when name is variable ([#286](https://github.com/prometheus/statsd_exporter/pull/286))
 * [BUGFIX] nil pointer dereference in UDP listener ([#287](https://github.com/prometheus/statsd_exporter/pull/287))
diff --git a/Makefile.common b/Makefile.common
index b978dfc..066866e 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -69,15 +69,6 @@ else
 	GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
 endif
 
-GOTEST := $(GO) test
-GOTEST_DIR :=
-ifneq ($(CIRCLE_JOB),)
-ifneq ($(shell which gotestsum),)
-	GOTEST_DIR := test-results
-	GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
-endif
-endif
-
 PROMU_VERSION ?= 0.5.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
@@ -151,17 +142,14 @@ else
 endif
 
 .PHONY: common-test-short
-common-test-short: $(GOTEST_DIR)
+common-test-short:
 	@echo ">> running short tests"
-	GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs)
+	GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs)
 
 .PHONY: common-test
-common-test: $(GOTEST_DIR)
+common-test:
 	@echo ">> running all tests"
-	GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
-
-$(GOTEST_DIR):
-	@mkdir -p $@
+	GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs)
 
 .PHONY: common-format
 common-format:
diff --git a/README.md b/README.md
index 830b57c..b992e8e 100644
--- a/README.md
+++ b/README.md
@@ -236,8 +236,7 @@ mappings:
 
 By default, statsd timers are represented as a Prometheus summary with
 quantiles. You may optionally configure the [quantiles and acceptable
-error](https://prometheus.io/docs/practices/histograms/#quantiles), as
-well as adjusting how the summary metric is aggregated:
+error](https://prometheus.io/docs/practices/histograms/#quantiles):
 
 ```yaml
 mappings:
@@ -248,29 +247,19 @@ mappings:
     provider: "$2"
     outcome: "$3"
     job: "${1}_server"
-  summary_options:
-    quantiles:
-      - quantile: 0.99
-        error: 0.001
-      - quantile: 0.95
-        error: 0.01
-      - quantile: 0.9
-        error: 0.05
-      - quantile: 0.5
-        error: 0.005
-    max_summary_age: 30s
-    summary_age_buckets: 3
-    stream_buffer_size: 1000
+  quantiles:
+    - quantile: 0.99
+      error: 0.001
+    - quantile: 0.95
+      error: 0.01
+    - quantile: 0.9
+      error: 0.05
+    - quantile: 0.5
+      error: 0.005
 ```
 
 The default quantiles are 0.99, 0.9, and 0.5.
 
-The default summary age is 10 minutes, the default number of buckets
-is 5 and the default buffer size is 500.  See also the
-[`golang_client` docs](https://godoc.org/github.com/prometheus/client_golang/prometheus#SummaryOpts).
-The `max_summary_age` corresponds to `SummaryOptions.MaxAge`, `summary_age_buckets`
-to `SummaryOptions.AgeBuckets` and `stream_buffer_size` to `SummaryOptions.BufCap`.
-
 In the configuration, one may also set the timer type to "histogram". The
 default is "summary" as in the plain text configuration format.  For example,
 to set the timer type for a single metric:
@@ -279,8 +268,7 @@ to set the timer type for a single metric:
 mappings:
 - match: "test.timing.*.*.*"
   timer_type: histogram
-  histogram_options:
-    buckets: [ 0.01, 0.025, 0.05, 0.1 ]
+  buckets: [ 0.01, 0.025, 0.05, 0.1 ]
   name: "my_timer"
   labels:
     provider: "$2"
diff --git a/VERSION b/VERSION
index a551051..930e300 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.15.0
+0.14.1
diff --git a/exporter.go b/exporter.go
index e3984b7..c70a12e 100644
--- a/exporter.go
+++ b/exporter.go
@@ -54,6 +54,61 @@ type Exporter struct {
 	logger   log.Logger
 }
 
+// Replace invalid characters in the metric name with "_"
+// Valid characters are a-z, A-Z, 0-9, and _
+func escapeMetricName(metricName string) string {
+	metricLen := len(metricName)
+	if metricLen == 0 {
+		return ""
+	}
+
+	escaped := false
+	var sb strings.Builder
+	// If a metric starts with a digit, allocate the memory and prepend an
+	// underscore.
+	if metricName[0] >= '0' && metricName[0] <= '9' {
+		escaped = true
+		sb.Grow(metricLen + 1)
+		sb.WriteByte('_')
+	}
+
+	// This is an character replacement method optimized for this limited
+	// use case.  It is much faster than using a regex.
+	offset := 0
+	for i, c := range metricName {
+		// Seek forward, skipping valid characters until we find one that needs
+		// to be replaced, then add all the characters we've seen so far to the
+		// string.Builder.
+		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+			(c >= '0' && c <= '9') || (c == '_') {
+			// Character is valid, so skip over it without doing anything.
+		} else {
+			if !escaped {
+				// Up until now we've been lazy and avoided actually allocating
+				// memory.  Unfortunately we've now determined this string needs
+				// escaping, so allocate the buffer for the whole string.
+				escaped = true
+				sb.Grow(metricLen)
+			}
+			sb.WriteString(metricName[offset:i])
+			offset = i + utf8.RuneLen(c)
+			sb.WriteByte('_')
+		}
+	}
+
+	if !escaped {
+		// This is the happy path where nothing had to be escaped, so we can
+		// avoid doing anything.
+		return metricName
+	}
+
+	if offset < metricLen {
+		sb.WriteString(metricName[offset:])
+	}
+
+	return sb.String()
+}
+
 // Listen handles all events sent to the given channel sequentially. It
 // terminates when the channel is closed.
 func (b *Exporter) Listen(e <-chan Events) {
@@ -104,14 +159,14 @@ func (b *Exporter) handleEvent(event Event) {
 			errorEventStats.WithLabelValues("empty_metric_name").Inc()
 			return
 		}
-		metricName = mapper.EscapeMetricName(mapping.Name)
+		metricName = escapeMetricName(mapping.Name)
 		for label, value := range labels {
 			prometheusLabels[label] = value
 		}
 		eventsActions.WithLabelValues(string(mapping.Action)).Inc()
 	} else {
 		eventsUnmapped.Inc()
-		metricName = mapper.EscapeMetricName(event.MetricName())
+		metricName = escapeMetricName(event.MetricName())
 	}
 
 	switch ev := event.(type) {
@@ -243,7 +298,7 @@ func parseTag(component, tag string, separator rune, labels map[string]string, l
 				tagErrors.Inc()
 				level.Debug(logger).Log("msg", "Malformed name tag", "k", k, "v", v, "component", component)
 			} else {
-				labels[mapper.EscapeMetricName(k)] = v
+				labels[escapeMetricName(k)] = v
 			}
 			return
 		}
diff --git a/main.go b/main.go
index 221b1ce..b36d3c4 100644
--- a/main.go
+++ b/main.go
@@ -101,7 +101,7 @@ func tcpAddrFromString(addr string) (*net.TCPAddr, error) {
 	}, nil
 }
 
-func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger) {
+func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger, option mapper.CacheOption) {
 
 	signals := make(chan os.Signal, 1)
 	signal.Notify(signals, syscall.SIGHUP)
@@ -112,7 +112,7 @@ func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int,
 			continue
 		}
 		level.Info(logger).Log("msg", "Received signal, attempting reload", "signal", s)
-		err := mapper.InitFromFile(fileName, cacheSize)
+		err := mapper.InitFromFile(fileName, cacheSize, option)
 		if err != nil {
 			level.Info(logger).Log("msg", "Error reloading config", "error", err)
 			configLoads.WithLabelValues("failure").Inc()
@@ -149,6 +149,7 @@ func main() {
 		mappingConfig        = kingpin.Flag("statsd.mapping-config", "Metric mapping configuration file name.").String()
 		readBuffer           = kingpin.Flag("statsd.read-buffer", "Size (in bytes) of the operating system's transmit read buffer associated with the UDP or Unixgram connection. Please make sure the kernel parameters net.core.rmem_max is set to a value greater than the value specified.").Int()
 		cacheSize            = kingpin.Flag("statsd.cache-size", "Maximum size of your metric mapping cache. Relies on least recently used replacement policy if max size is reached.").Default("1000").Int()
+		cacheType            = kingpin.Flag("statsd.cache-type", "Metric mapping cache type. Valid options are \"lru\" and \"random\"").Default("lru").Enum("lru", "random")
 		eventQueueSize       = kingpin.Flag("statsd.event-queue-size", "Size of internal queue for processing events").Default("10000").Int()
 		eventFlushThreshold  = kingpin.Flag("statsd.event-flush-threshold", "Number of events to hold in queue before flushing").Default("1000").Int()
 		eventFlushInterval   = kingpin.Flag("statsd.event-flush-interval", "Number of events to hold in queue before flushing").Default("200ms").Duration()
@@ -162,6 +163,8 @@ func main() {
 	kingpin.Parse()
 	logger := promlog.New(promlogConfig)
 
+	cacheOption := mapper.WithCacheType(*cacheType)
+
 	if *statsdListenUDP == "" && *statsdListenTCP == "" && *statsdListenUnixgram == "" {
 		level.Error(logger).Log("At least one of UDP/TCP/Unixgram listeners must be specified.")
 		os.Exit(1)
@@ -268,7 +271,7 @@ func main() {
 
 	mapper := &mapper.MetricMapper{MappingsCount: mappingsCount}
 	if *mappingConfig != "" {
-		err := mapper.InitFromFile(*mappingConfig, *cacheSize)
+		err := mapper.InitFromFile(*mappingConfig, *cacheSize, cacheOption)
 		if err != nil {
 			level.Error(logger).Log("msg", "error loading config", "error", err)
 			os.Exit(1)
@@ -283,10 +286,10 @@ func main() {
 			}
 		}
 	} else {
-		mapper.InitCache(*cacheSize)
+		mapper.InitCache(*cacheSize, cacheOption)
 	}
 
-	go configReloader(*mappingConfig, mapper, *cacheSize, logger)
+	go configReloader(*mappingConfig, mapper, *cacheSize, logger, cacheOption)
 
 	exporter := NewExporter(mapper, logger)
 
diff --git a/pkg/mapper/escape.go b/pkg/mapper/escape.go
deleted file mode 100644
index fc8d194..0000000
--- a/pkg/mapper/escape.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2020 The Prometheus Authors
-// 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 mapper
-
-import (
-	"strings"
-	"unicode/utf8"
-)
-
-// EscapeMetricName replaces invalid characters in the metric name with "_"
-// Valid characters are a-z, A-Z, 0-9, and _
-func EscapeMetricName(metricName string) string {
-	metricLen := len(metricName)
-	if metricLen == 0 {
-		return ""
-	}
-
-	escaped := false
-	var sb strings.Builder
-	// If a metric starts with a digit, allocate the memory and prepend an
-	// underscore.
-	if metricName[0] >= '0' && metricName[0] <= '9' {
-		escaped = true
-		sb.Grow(metricLen + 1)
-		sb.WriteByte('_')
-	}
-
-	// This is an character replacement method optimized for this limited
-	// use case.  It is much faster than using a regex.
-	offset := 0
-	for i, c := range metricName {
-		// Seek forward, skipping valid characters until we find one that needs
-		// to be replaced, then add all the characters we've seen so far to the
-		// string.Builder.
-		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
-			(c >= '0' && c <= '9') || (c == '_') {
-			// Character is valid, so skip over it without doing anything.
-		} else {
-			if !escaped {
-				// Up until now we've been lazy and avoided actually allocating
-				// memory.  Unfortunately we've now determined this string needs
-				// escaping, so allocate the buffer for the whole string.
-				escaped = true
-				sb.Grow(metricLen)
-			}
-			sb.WriteString(metricName[offset:i])
-			offset = i + utf8.RuneLen(c)
-			sb.WriteByte('_')
-		}
-	}
-
-	if !escaped {
-		// This is the happy path where nothing had to be escaped, so we can
-		// avoid doing anything.
-		return metricName
-	}
-
-	if offset < metricLen {
-		sb.WriteString(metricName[offset:])
-	}
-
-	return sb.String()
-}
diff --git a/pkg/mapper/mapper.go b/pkg/mapper/mapper.go
index 61814ad..e87ebff 100644
--- a/pkg/mapper/mapper.go
+++ b/pkg/mapper/mapper.go
@@ -18,12 +18,12 @@ import (
 	"io/ioutil"
 	"regexp"
 	"sync"
+	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/common/log"
 	"github.com/prometheus/statsd_exporter/pkg/mapper/fsm"
 	yaml "gopkg.in/yaml.v2"
-	"time"
 )
 
 var (
@@ -57,34 +57,21 @@ type MetricMapper struct {
 }
 
 type MetricMapping struct {
-	Match            string `yaml:"match"`
-	Name             string `yaml:"name"`
-	nameFormatter    *fsm.TemplateFormatter
-	regex            *regexp.Regexp
-	Labels           prometheus.Labels `yaml:"labels"`
-	labelKeys        []string
-	labelFormatters  []*fsm.TemplateFormatter
-	TimerType        TimerType         `yaml:"timer_type"`
-	LegacyBuckets    []float64         `yaml:"buckets"`
-	LegacyQuantiles  []metricObjective `yaml:"quantiles"`
-	MatchType        MatchType         `yaml:"match_type"`
-	HelpText         string            `yaml:"help"`
-	Action           ActionType        `yaml:"action"`
-	MatchMetricType  MetricType        `yaml:"match_metric_type"`
-	Ttl              time.Duration     `yaml:"ttl"`
-	SummaryOptions   *SummaryOptions   `yaml:"summary_options"`
-	HistogramOptions *HistogramOptions `yaml:"histogram_options"`
-}
-
-type SummaryOptions struct {
-	Quantiles  []metricObjective `yaml:"quantiles"`
-	MaxAge     time.Duration     `yaml:"max_age"`
-	AgeBuckets uint32            `yaml:"age_buckets"`
-	BufCap     uint32            `yaml:"buf_cap"`
-}
-
-type HistogramOptions struct {
-	Buckets []float64 `yaml:"buckets"`
+	Match           string `yaml:"match"`
+	Name            string `yaml:"name"`
+	nameFormatter   *fsm.TemplateFormatter
+	regex           *regexp.Regexp
+	Labels          prometheus.Labels `yaml:"labels"`
+	labelKeys       []string
+	labelFormatters []*fsm.TemplateFormatter
+	TimerType       TimerType         `yaml:"timer_type"`
+	Buckets         []float64         `yaml:"buckets"`
+	Quantiles       []metricObjective `yaml:"quantiles"`
+	MatchType       MatchType         `yaml:"match_type"`
+	HelpText        string            `yaml:"help"`
+	Action          ActionType        `yaml:"action"`
+	MatchMetricType MetricType        `yaml:"match_metric_type"`
+	Ttl             time.Duration     `yaml:"ttl"`
 }
 
 type metricObjective struct {
@@ -98,7 +85,7 @@ var defaultQuantiles = []metricObjective{
 	{Quantile: 0.99, Error: 0.001},
 }
 
-func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) error {
+func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int, options ...CacheOption) error {
 	var n MetricMapper
 
 	if err := yaml.Unmarshal([]byte(fileContents), &n); err != nil {
@@ -185,56 +172,12 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) er
 			currentMapping.TimerType = n.Defaults.TimerType
 		}
 
-		if currentMapping.LegacyQuantiles != nil &&
-			(currentMapping.SummaryOptions == nil || currentMapping.SummaryOptions.Quantiles != nil) {
-			log.Warn("using the top level quantiles is deprecated.  Please use quantiles in the summary_options hierarchy")
-		}
-
-		if currentMapping.LegacyBuckets != nil &&
-			(currentMapping.HistogramOptions == nil || currentMapping.HistogramOptions.Buckets != nil) {
-			log.Warn("using the top level buckets is deprecated.  Please use buckets in the histogram_options hierarchy")
-		}
-
-		if currentMapping.SummaryOptions != nil &&
-			currentMapping.LegacyQuantiles != nil &&
-			currentMapping.SummaryOptions.Quantiles != nil {
-			return fmt.Errorf("cannot use quantiles in both the top level and summary options at the same time in %s", currentMapping.Match)
+		if currentMapping.Buckets == nil || len(currentMapping.Buckets) == 0 {
+			currentMapping.Buckets = n.Defaults.Buckets
 		}
 
-		if currentMapping.HistogramOptions != nil &&
-			currentMapping.LegacyBuckets != nil &&
-			currentMapping.HistogramOptions.Buckets != nil {
-			return fmt.Errorf("cannot use buckets in both the top level and histogram options at the same time in %s", currentMapping.Match)
-		}
-
-		if currentMapping.TimerType == TimerTypeHistogram {
-			if currentMapping.SummaryOptions != nil {
-				return fmt.Errorf("cannot use histogram timer and summary options at the same time")
-			}
-			if currentMapping.HistogramOptions == nil {
-				currentMapping.HistogramOptions = &HistogramOptions{}
-			}
-			if currentMapping.LegacyBuckets != nil && len(currentMapping.LegacyBuckets) != 0 {
-				currentMapping.HistogramOptions.Buckets = currentMapping.LegacyBuckets
-			}
-			if currentMapping.HistogramOptions.Buckets == nil || len(currentMapping.HistogramOptions.Buckets) == 0 {
-				currentMapping.HistogramOptions.Buckets = n.Defaults.Buckets
-			}
-		}
-
-		if currentMapping.TimerType == TimerTypeSummary {
-			if currentMapping.HistogramOptions != nil {
-				return fmt.Errorf("cannot use summary timer and histogram options at the same time")
-			}
-			if currentMapping.SummaryOptions == nil {
-				currentMapping.SummaryOptions = &SummaryOptions{}
-			}
-			if currentMapping.LegacyQuantiles != nil && len(currentMapping.LegacyQuantiles) != 0 {
-				currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles
-			}
-			if currentMapping.SummaryOptions.Quantiles == nil || len(currentMapping.SummaryOptions.Quantiles) == 0 {
-				currentMapping.SummaryOptions.Quantiles = n.Defaults.Quantiles
-			}
+		if currentMapping.Quantiles == nil || len(currentMapping.Quantiles) == 0 {
+			currentMapping.Quantiles = n.Defaults.Quantiles
 		}
 
 		if currentMapping.Ttl == 0 && n.Defaults.Ttl > 0 {
@@ -248,7 +191,7 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) er
 
 	m.Defaults = n.Defaults
 	m.Mappings = n.Mappings
-	m.InitCache(cacheSize)
+	m.InitCache(cacheSize, options...)
 
 	if n.doFSM {
 		var mappings []string
@@ -270,20 +213,39 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) er
 	return nil
 }
 
-func (m *MetricMapper) InitFromFile(fileName string, cacheSize int) error {
+func (m *MetricMapper) InitFromFile(fileName string, cacheSize int, options ...CacheOption) error {
 	mappingStr, err := ioutil.ReadFile(fileName)
 	if err != nil {
 		return err
 	}
 
-	return m.InitFromYAMLString(string(mappingStr), cacheSize)
+	return m.InitFromYAMLString(string(mappingStr), cacheSize, options...)
 }
 
-func (m *MetricMapper) InitCache(cacheSize int) {
+func (m *MetricMapper) InitCache(cacheSize int, options ...CacheOption) {
 	if cacheSize == 0 {
 		m.cache = NewMetricMapperNoopCache()
 	} else {
-		cache, err := NewMetricMapperCache(cacheSize)
+		o := cacheOptions{
+			cacheType: "lru",
+		}
+		for _, f := range options {
+			f(&o)
+		}
+
+		var (
+			cache MetricMapperCache
+			err   error
+		)
+		switch o.cacheType {
+		case "lru":
+			cache, err = NewMetricMapperCache(cacheSize)
+		case "random":
+			cache, err = NewMetricMapperRRCache(cacheSize)
+		default:
+			err = fmt.Errorf("unsupported cache type %q", o.cacheType)
+		}
+
 		if err != nil {
 			log.Fatalf("Unable to setup metric cache. Caused by: %s", err)
 		}
diff --git a/pkg/mapper/mapper_cache.go b/pkg/mapper/mapper_cache.go
index 1452d65..5b252a7 100644
--- a/pkg/mapper/mapper_cache.go
+++ b/pkg/mapper/mapper_cache.go
@@ -14,6 +14,8 @@
 package mapper
 
 import (
+	"sync"
+
 	lru "github.com/hashicorp/golang-lru"
 	"github.com/prometheus/client_golang/prometheus"
 )
@@ -39,6 +41,18 @@ var (
 	)
 )
 
+type cacheOptions struct {
+	cacheType string
+}
+
+type CacheOption func(*cacheOptions)
+
+func WithCacheType(cacheType string) CacheOption {
+	return func(o *cacheOptions) {
+		o.cacheType = cacheType
+	}
+}
+
 type MetricMapperCacheResult struct {
 	Mapping *MetricMapping
 	Matched bool
@@ -114,6 +128,69 @@ func (m *MetricMapperNoopCache) AddMiss(metricString string, metricType MetricTy
 	return
 }
 
+type MetricMapperRRCache struct {
+	MetricMapperCache
+	lock  sync.RWMutex
+	size  int
+	items map[string]*MetricMapperCacheResult
+}
+
+func NewMetricMapperRRCache(size int) (*MetricMapperRRCache, error) {
+	cacheLength.Set(0)
+	c := &MetricMapperRRCache{
+		items: make(map[string]*MetricMapperCacheResult, size+1),
+		size:  size,
+	}
+	return c, nil
+}
+
+func (m *MetricMapperRRCache) Get(metricString string, metricType MetricType) (*MetricMapperCacheResult, bool) {
+	key := formatKey(metricString, metricType)
+
+	m.lock.RLock()
+	result, ok := m.items[key]
+	m.lock.RUnlock()
+
+	return result, ok
+}
+
+func (m *MetricMapperRRCache) addItem(metricString string, metricType MetricType, result *MetricMapperCacheResult) {
+	go m.trackCacheLength()
+
+	key := formatKey(metricString, metricType)
+
+	m.lock.Lock()
+
+	m.items[key] = result
+
+	// evict an item if needed
+	if len(m.items) > m.size {
+		for k := range m.items {
+			delete(m.items, k)
+			break
+		}
+	}
+
+	m.lock.Unlock()
+}
+
+func (m *MetricMapperRRCache) AddMatch(metricString string, metricType MetricType, mapping *MetricMapping, labels prometheus.Labels) {
+	e := &MetricMapperCacheResult{Mapping: mapping, Matched: true, Labels: labels}
+	m.addItem(metricString, metricType, e)
+}
+
+func (m *MetricMapperRRCache) AddMiss(metricString string, metricType MetricType) {
+	e := &MetricMapperCacheResult{Matched: false}
+	m.addItem(metricString, metricType, e)
+}
+
+func (m *MetricMapperRRCache) trackCacheLength() {
+	m.lock.RLock()
+	length := len(m.items)
+	m.lock.RUnlock()
+	cacheLength.Set(float64(length))
+}
+
 func init() {
 	prometheus.MustRegister(cacheLength)
 	prometheus.MustRegister(cacheGetsTotal)
diff --git a/registry.go b/registry.go
index d29dbce..df16dee 100644
--- a/registry.go
+++ b/registry.go
@@ -278,8 +278,8 @@ func (r *registry) getHistogram(metricName string, labels prometheus.Labels, hel
 	if vh == nil {
 		metricsCount.WithLabelValues("histogram").Inc()
 		buckets := r.mapper.Defaults.Buckets
-		if mapping.HistogramOptions != nil && len(mapping.HistogramOptions.Buckets) > 0 {
-			buckets = mapping.HistogramOptions.Buckets
+		if mapping.Buckets != nil && len(mapping.Buckets) > 0 {
+			buckets = mapping.Buckets
 		}
 		histogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
 			Name:    metricName,
@@ -325,12 +325,8 @@ func (r *registry) getSummary(metricName string, labels prometheus.Labels, help
 	if vh == nil {
 		metricsCount.WithLabelValues("summary").Inc()
 		quantiles := r.mapper.Defaults.Quantiles
-		if mapping != nil && mapping.SummaryOptions != nil && len(mapping.SummaryOptions.Quantiles) > 0 {
-			quantiles = mapping.SummaryOptions.Quantiles
-		}
-		summaryOptions := mapper.SummaryOptions{}
-		if mapping != nil && mapping.SummaryOptions != nil {
-			summaryOptions = *mapping.SummaryOptions
+		if mapping != nil && mapping.Quantiles != nil && len(mapping.Quantiles) > 0 {
+			quantiles = mapping.Quantiles
 		}
 		objectives := make(map[float64]float64)
 		for _, q := range quantiles {
@@ -344,9 +340,6 @@ func (r *registry) getSummary(metricName string, labels prometheus.Labels, help
 			Name:       metricName,
 			Help:       help,
 			Objectives: objectives,
-			MaxAge:     summaryOptions.MaxAge,
-			AgeBuckets: summaryOptions.AgeBuckets,
-			BufCap:     summaryOptions.BufCap,
 		}, labelNames)
 
 		if err := prometheus.Register(uncheckedCollector{summaryVec}); err != nil {


Test patch:
diff --git a/exporter_test.go b/exporter_test.go
index c64a5a2..ffc1d27 100644
--- a/exporter_test.go
+++ b/exporter_test.go
@@ -682,6 +682,25 @@ func (ml *mockStatsDTCPListener) handlePacket(packet []byte) {
 	ml.handleConn(sc)
 }
 
+func TestEscapeMetricName(t *testing.T) {
+	scenarios := map[string]string{
+		"clean":                   "clean",
+		"0starts_with_digit":      "_0starts_with_digit",
+		"with_underscore":         "with_underscore",
+		"with.dot":                "with_dot",
+		"with😱emoji":              "with_emoji",
+		"with.*.multiple":         "with___multiple",
+		"test.web-server.foo.bar": "test_web_server_foo_bar",
+		"":                        "",
+	}
+
+	for in, want := range scenarios {
+		if got := escapeMetricName(in); want != got {
+			t.Errorf("expected `%s` to be escaped to `%s`, got `%s`", in, want, got)
+		}
+	}
+}
+
 // TestTtlExpiration validates expiration of time series.
 // foobar metric without mapping should expire with default ttl of 1s
 // bazqux metric should expire with ttl of 2s
@@ -903,6 +922,27 @@ func getTelemetryCounterValue(counter prometheus.Counter) float64 {
 	return metric.Counter.GetValue()
 }
 
+func BenchmarkEscapeMetricName(b *testing.B) {
+	scenarios := []string{
+		"clean",
+		"0starts_with_digit",
+		"with_underscore",
+		"with.dot",
+		"with😱emoji",
+		"with.*.multiple",
+		"test.web-server.foo.bar",
+		"",
+	}
+
+	for _, s := range scenarios {
+		b.Run(s, func(b *testing.B) {
+			for n := 0; n < b.N; n++ {
+				escapeMetricName(s)
+			}
+		})
+	}
+}
+
 func BenchmarkParseDogStatsDTags(b *testing.B) {
 	scenarios := map[string]string{
 		"1 tag w/hash":         "#test:tag",
diff --git a/pkg/mapper/escape_test.go b/pkg/mapper/escape_test.go
deleted file mode 100644
index 336692d..0000000
--- a/pkg/mapper/escape_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 The Prometheus Authors
-// 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 mapper
-
-import "testing"
-
-func TestEscapeMetricName(t *testing.T) {
-	scenarios := map[string]string{
-		"clean":                   "clean",
-		"0starts_with_digit":      "_0starts_with_digit",
-		"with_underscore":         "with_underscore",
-		"with.dot":                "with_dot",
-		"with😱emoji":              "with_emoji",
-		"with.*.multiple":         "with___multiple",
-		"test.web-server.foo.bar": "test_web_server_foo_bar",
-		"":                        "",
-	}
-
-	for in, want := range scenarios {
-		if got := EscapeMetricName(in); want != got {
-			t.Errorf("expected `%s` to be escaped to `%s`, got `%s`", in, want, got)
-		}
-	}
-}
-
-func BenchmarkEscapeMetricName(b *testing.B) {
-	scenarios := []string{
-		"clean",
-		"0starts_with_digit",
-		"with_underscore",
-		"with.dot",
-		"with😱emoji",
-		"with.*.multiple",
-		"test.web-server.foo.bar",
-		"",
-	}
-
-	for _, s := range scenarios {
-		b.Run(s, func(b *testing.B) {
-			for n := 0; n < b.N; n++ {
-				EscapeMetricName(s)
-			}
-		})
-	}
-}
diff --git a/pkg/mapper/mapper_benchmark_test.go b/pkg/mapper/mapper_benchmark_test.go
index fe76d78..9210571 100644
--- a/pkg/mapper/mapper_benchmark_test.go
+++ b/pkg/mapper/mapper_benchmark_test.go
@@ -15,6 +15,7 @@ package mapper
 
 import (
 	"fmt"
+	"math/rand"
 	"testing"
 )
 
@@ -580,17 +581,21 @@ mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob)
 		"metric100.a",
 	}
 
-	mapper := MetricMapper{}
-	err := mapper.InitFromYAMLString(config, 1000)
-	if err != nil {
-		b.Fatalf("Config load error: %s %s", config, err)
-	}
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 1000, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
 
-	b.ResetTimer()
-	for j := 0; j < b.N; j++ {
-		for _, metric := range mappings {
-			mapper.GetMapping(metric, MetricTypeCounter)
-		}
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
 	}
 }
 
@@ -626,17 +631,21 @@ mappings:` + duplicateRules(10, ruleTemplateSingleMatchRegex)
 		"metric5.a",
 	}
 
-	mapper := MetricMapper{}
-	err := mapper.InitFromYAMLString(config, 1000)
-	if err != nil {
-		b.Fatalf("Config load error: %s %s", config, err)
-	}
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 1000, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
 
-	b.ResetTimer()
-	for j := 0; j < b.N; j++ {
-		for _, metric := range mappings {
-			mapper.GetMapping(metric, MetricTypeCounter)
-		}
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
 	}
 }
 
@@ -668,17 +677,21 @@ mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob)
 		"metric100.a",
 	}
 
-	mapper := MetricMapper{}
-	err := mapper.InitFromYAMLString(config, 1000)
-	if err != nil {
-		b.Fatalf("Config load error: %s %s", config, err)
-	}
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 1000, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
 
-	b.ResetTimer()
-	for j := 0; j < b.N; j++ {
-		for _, metric := range mappings {
-			mapper.GetMapping(metric, MetricTypeCounter)
-		}
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
 	}
 }
 
@@ -800,17 +813,21 @@ mappings:` + duplicateRules(100, ruleTemplateMultipleMatchGlob)
 		"metric50.a.b.c.d.e.f.g.h.i.j.k.l",
 	}
 
-	mapper := MetricMapper{}
-	err := mapper.InitFromYAMLString(config, 1000)
-	if err != nil {
-		b.Fatalf("Config load error: %s %s", config, err)
-	}
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 1000, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
 
-	b.ResetTimer()
-	for j := 0; j < b.N; j++ {
-		for _, metric := range mappings {
-			mapper.GetMapping(metric, MetricTypeCounter)
-		}
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
 	}
 }
 
@@ -869,16 +886,115 @@ mappings:` + duplicateRules(100, ruleTemplateMultipleMatchRegex)
 		"metric100.a.b.c.d.e.f.g.h.i.j.k.l",
 	}
 
-	mapper := MetricMapper{}
-	err := mapper.InitFromYAMLString(config, 1000)
-	if err != nil {
-		b.Fatalf("Config load error: %s %s", config, err)
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 1000, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
+
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
 	}
+}
 
-	b.ResetTimer()
-	for j := 0; j < b.N; j++ {
-		for _, metric := range mappings {
-			mapper.GetMapping(metric, MetricTypeCounter)
-		}
+func duplicateMetrics(count int, template string) []string {
+	var out []string
+	for i := 0; i < count; i++ {
+		out = append(out, fmt.Sprintf(template, i))
+	}
+	return out
+}
+
+func BenchmarkGlob100RulesCached100Metrics(b *testing.B) {
+	config := `---
+mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob)
+
+	mappings := duplicateMetrics(100, "metric100")
+
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 1000, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
+
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
+	}
+}
+
+func BenchmarkGlob100RulesCached100MetricsSmallCache(b *testing.B) {
+	// This benchmark is the worst case for the LRU cache.
+	// The cache is smaller than the total number of metrics and
+	// we iterate linearly through the metrics, so we will
+	// constantly evict cache entries.
+	config := `---
+mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob)
+
+	mappings := duplicateMetrics(100, "metric100")
+
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 50, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
+
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
+	}
+}
+
+func BenchmarkGlob100RulesCached100MetricsRandomSmallCache(b *testing.B) {
+	// Slighly more realistic benchmark with a smaller cache.
+	// Randomly match metrics so we should have some cache hits.
+	config := `---
+mappings:` + duplicateRules(100, ruleTemplateSingleMatchGlob)
+
+	base := duplicateMetrics(100, "metric100")
+	var mappings []string
+	for i := 0; i < 10; i++ {
+		mappings = append(mappings, base...)
+	}
+
+	r := rand.New(rand.NewSource(42))
+	r.Shuffle(len(mappings), func(i, j int) {
+		mappings[i], mappings[j] = mappings[j], mappings[i]
+	})
+
+	for _, cacheType := range []string{"lru", "random"} {
+		b.Run(cacheType, func(b *testing.B) {
+			mapper := MetricMapper{}
+			err := mapper.InitFromYAMLString(config, 50, WithCacheType(cacheType))
+			if err != nil {
+				b.Fatalf("Config load error: %s %s", config, err)
+			}
+
+			b.ResetTimer()
+			for j := 0; j < b.N; j++ {
+				for _, metric := range mappings {
+					mapper.GetMapping(metric, MetricTypeCounter)
+				}
+			}
+		})
 	}
 }
diff --git a/pkg/mapper/mapper_test.go b/pkg/mapper/mapper_test.go
index dc8a45d..3eb0d1c 100644
--- a/pkg/mapper/mapper_test.go
+++ b/pkg/mapper/mapper_test.go
@@ -26,9 +26,6 @@ type mappings []struct {
 	notPresent   bool
 	ttl          time.Duration
 	metricType   MetricType
-	maxAge       time.Duration
-	ageBuckets   uint32
-	bufCap       uint32
 }
 
 func TestMetricMapperYAML(t *testing.T) {
@@ -499,84 +496,6 @@ mappings:
     `,
 			configBad: true,
 		},
-		// new style quantiles
-		{
-			config: `---
-mappings:
-- match: test.*.*
-  timer_type: summary
-  name: "foo"
-  labels: {}
-  summary_options:
-    quantiles:
-      - quantile: 0.42
-        error: 0.04
-      - quantile: 0.7
-        error: 0.002
-  `,
-			mappings: mappings{
-				{
-					statsdMetric: "test.*.*",
-					name:         "foo",
-					labels:       map[string]string{},
-					quantiles: []metricObjective{
-						{Quantile: 0.42, Error: 0.04},
-						{Quantile: 0.7, Error: 0.002},
-					},
-				},
-			},
-		},
-		// Config with summary configuration.
-		{
-			config: `---
-mappings:
-- match: test.*.*
-  timer_type: summary
-  name: "foo"
-  labels: {}
-  summary_options:
-    quantiles:
-      - quantile: 0.42
-        error: 0.04
-      - quantile: 0.7
-        error: 0.002
-    max_age: 5m
-    age_buckets: 2
-    buf_cap: 1000
-  `,
-			mappings: mappings{
-				{
-					statsdMetric: "test.*.*",
-					name:         "foo",
-					labels:       map[string]string{},
-					quantiles: []metricObjective{
-						{Quantile: 0.42, Error: 0.04},
-						{Quantile: 0.7, Error: 0.002},
-					},
-					maxAge:     5 * time.Minute,
-					ageBuckets: 2,
-					bufCap:     1000,
-				},
-			},
-		},
-		// duplicate quantiles are bad
-		{
-			config: `---
-mappings:
-- match: test.*.*
-  timer_type: summary
-  name: "foo"
-  labels: {}
-  quantiles:
-    - quantile: 0.42
-      error: 0.04
-  summary_options:
-    quantiles:
-      - quantile: 0.42
-        error: 0.04
-  `,
-			configBad: true,
-		},
 		// Config with good metric type.
 		{
 			config: `---
@@ -858,27 +777,18 @@ mappings:
 			}
 
 			if len(mapping.quantiles) != 0 {
-				if len(mapping.quantiles) != len(m.SummaryOptions.Quantiles) {
-					t.Fatalf("%d.%q: Expected %d quantiles, got %d", i, metric, len(mapping.quantiles), len(m.SummaryOptions.Quantiles))
+				if len(mapping.quantiles) != len(m.Quantiles) {
+					t.Fatalf("%d.%q: Expected %d quantiles, got %d", i, metric, len(mapping.quantiles), len(m.Quantiles))
 				}
 				for i, quantile := range mapping.quantiles {
-					if quantile.Quantile != m.SummaryOptions.Quantiles[i].Quantile {
-						t.Fatalf("%d.%q: Expected quantile %v, got %v", i, metric, m.SummaryOptions.Quantiles[i].Quantile, quantile.Quantile)
+					if quantile.Quantile != m.Quantiles[i].Quantile {
+						t.Fatalf("%d.%q: Expected quantile %v, got %v", i, metric, m.Quantiles[i].Quantile, quantile.Quantile)
 					}
-					if quantile.Error != m.SummaryOptions.Quantiles[i].Error {
-						t.Fatalf("%d.%q: Expected Error margin %v, got %v", i, metric, m.SummaryOptions.Quantiles[i].Error, quantile.Error)
+					if quantile.Error != m.Quantiles[i].Error {
+						t.Fatalf("%d.%q: Expected Error margin %v, got %v", i, metric, m.Quantiles[i].Error, quantile.Error)
 					}
 				}
 			}
-			if mapping.maxAge != 0 && mapping.maxAge != m.SummaryOptions.MaxAge {
-				t.Fatalf("%d.%q: Expected max age %v, got %v", i, metric, mapping.maxAge, m.SummaryOptions.MaxAge)
-			}
-			if mapping.ageBuckets != 0 && mapping.ageBuckets != m.SummaryOptions.AgeBuckets {
-				t.Fatalf("%d.%q: Expected max age %v, got %v", i, metric, mapping.ageBuckets, m.SummaryOptions.AgeBuckets)
-			}
-			if mapping.bufCap != 0 && mapping.bufCap != m.SummaryOptions.BufCap {
-				t.Fatalf("%d.%q: Expected max age %v, got %v", i, metric, mapping.bufCap, m.SummaryOptions.BufCap)
-			}
 		}
 	}
 }