Merge lp:~jerith/graphite/storage-aggregation into lp:~graphite-dev/graphite/main

Proposed by Jeremy Thurgood
Status: Merged
Merge reported by: chrismd
Merged at revision: not available
Proposed branch: lp:~jerith/graphite/storage-aggregation
Merge into: lp:~graphite-dev/graphite/main
Diff against target: 201 lines (+98/-5)
4 files modified
carbon/lib/carbon/storage.py (+48/-0)
carbon/lib/carbon/writer.py (+11/-2)
docs/config-carbon.rst (+32/-1)
whisper/whisper.py (+7/-2)
To merge this branch: bzr merge lp:~jerith/graphite/storage-aggregation
Reviewer Review Type Date Requested Status
chrismd Approve
Review via email: mp+76355@code.launchpad.net

Description of the change

This branch adds an optional storage-aggregation.conf to specify whisper retention aggregation parameters based on metric names.

To post a comment you must log in.
Revision history for this message
chrismd (chrismd) wrote :

Looks great, thanks for contributing this, I've merged it into trunk.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'carbon/lib/carbon/storage.py'
2--- carbon/lib/carbon/storage.py 2011-09-11 06:43:44 +0000
3+++ carbon/lib/carbon/storage.py 2011-09-21 08:56:30 +0000
4@@ -26,6 +26,7 @@
5
6
7 STORAGE_SCHEMAS_CONFIG = join(settings.CONF_DIR, 'storage-schemas.conf')
8+STORAGE_AGGREGATION_CONFIG = join(settings.CONF_DIR, 'storage-aggregation.conf')
9 STORAGE_LISTS_DIR = join(settings.CONF_DIR, 'lists')
10
11 def getFilesystemPath(metric):
12@@ -146,5 +147,52 @@
13 schemaList.append(defaultSchema)
14 return schemaList
15
16+
17+def loadAggregationSchemas():
18+ # NOTE: This abuses the Schema classes above, and should probably be refactored.
19+ schemaList = []
20+ config = OrderedConfigParser()
21+
22+ try:
23+ config.read(STORAGE_AGGREGATION_CONFIG)
24+ except IOError:
25+ log.msg("%s not found, ignoring." % STORAGE_AGGREGATION_CONFIG)
26+
27+ for section in config.sections():
28+ options = dict( config.items(section) )
29+ matchAll = options.get('match-all')
30+ pattern = options.get('pattern')
31+ listName = options.get('list')
32+
33+ xFilesFactor = options.get('xfilesfactor')
34+ aggregationMethod = options.get('aggregationmethod')
35+
36+ try:
37+ if xFilesFactor is not None:
38+ xFilesFactor = float(xFilesFactor)
39+ assert 0 <= xFilesFactor <= 1
40+ if aggregationMethod is not None:
41+ assert aggregationMethod in whisper.aggregationMethods
42+ except:
43+ log.msg("Invalid schemas found in %s." % section )
44+ continue
45+
46+ archives = (xFilesFactor, aggregationMethod)
47+
48+ if matchAll:
49+ mySchema = DefaultSchema(section, archives)
50+
51+ elif pattern:
52+ mySchema = PatternSchema(section, pattern, archives)
53+
54+ elif listName:
55+ mySchema = ListSchema(section, listName, archives)
56+
57+ schemaList.append(mySchema)
58+
59+ schemaList.append(defaultAggregation)
60+ return schemaList
61+
62 defaultArchive = Archive(60, 60 * 24 * 7) #default retention for unclassified data (7 days of minutely data)
63 defaultSchema = DefaultSchema('default', [defaultArchive])
64+defaultAggregation = DefaultSchema('default', (None, None))
65
66=== modified file 'carbon/lib/carbon/writer.py'
67--- carbon/lib/carbon/writer.py 2011-09-18 09:55:35 +0000
68+++ carbon/lib/carbon/writer.py 2011-09-21 08:56:30 +0000
69@@ -26,7 +26,7 @@
70 import whisper
71
72 from carbon.cache import MetricCache
73-from carbon.storage import getFilesystemPath, loadStorageSchemas
74+from carbon.storage import getFilesystemPath, loadStorageSchemas, loadAggregationSchemas
75 from carbon.conf import settings
76 from carbon import log, events, instrumentation
77
78@@ -38,6 +38,7 @@
79 lastCreateInterval = 0
80 createCount = 0
81 schemas = loadStorageSchemas()
82+agg_schemas = loadAggregationSchemas()
83 CACHE_SIZE_LOW_WATERMARK = settings.MAX_CACHE_SIZE * 0.95
84
85
86@@ -98,6 +99,7 @@
87
88 if not dbFileExists:
89 archiveConfig = None
90+ xFilesFactor, aggregationMethod = None, None
91
92 for schema in schemas:
93 if schema.matches(metric):
94@@ -105,6 +107,12 @@
95 archiveConfig = [archive.getTuple() for archive in schema.archives]
96 break
97
98+ for schema in agg_schemas:
99+ if schema.matches(metric):
100+ log.creates('new metric %s matched aggregation schema %s' % (metric, schema.name))
101+ xFilesFactor, aggregationMethod = schema.archives
102+ break
103+
104 if not archiveConfig:
105 raise Exception("No storage schema matched the metric '%s', check your storage-schemas.conf file." % metric)
106
107@@ -112,7 +120,8 @@
108 os.system("mkdir -p -m 755 '%s'" % dbDir)
109
110 log.creates("creating database file %s" % dbFilePath)
111- whisper.create(dbFilePath, archiveConfig)
112+ log.creates(" %s, %s, %s" % (archiveConfig, xFilesFactor, aggregationMethod))
113+ whisper.create(dbFilePath, archiveConfig, xFilesFactor, aggregationMethod)
114 os.chmod(dbFilePath, 0755)
115 instrumentation.increment('creates')
116
117
118=== modified file 'docs/config-carbon.rst'
119--- docs/config-carbon.rst 2011-09-08 11:37:13 +0000
120+++ docs/config-carbon.rst 2011-09-21 08:56:30 +0000
121@@ -49,7 +49,7 @@
122
123 The pattern matches server names that start with 'www', followed by anything, that end in '.workers.busyWorkers'. This way not all metrics associated with your webservers need this type of retention.
124
125-As you can see there are multiple retentions. Each is used in the order that it is provided. As a general rule, they should be in most-precise:shortest-length to least-precise:longest-time. Retentions are merely a way to save you disk space and decrease I/O for graphs that span a long period of time. When data moves from a higher precision to a lower precision, it is **averaged**. This way, you can still find the **total** for a particular time period if you know the original precision.
126+As you can see there are multiple retentions. Each is used in the order that it is provided. As a general rule, they should be in most-precise:shortest-length to least-precise:longest-time. Retentions are merely a way to save you disk space and decrease I/O for graphs that span a long period of time. By default, when data moves from a higher precision to a lower precision, it is **averaged**. This way, you can still find the **total** for a particular time period if you know the original precision. (To change the aggregation method, see the next section.)
127
128 Example: You store the number of sales per minute for 1 year, and the sales per hour for 5 years after that. You need to know the total sales for January 1st of the year before. You can query whisper for the raw data, and you'll get 24 datapoints, one for each hour. They will most likely be floating point numbers. You can take each datapoint, multiply by 60 (the ratio of high-precision to low-precision datapoints) and still get the total sales per hour.
129
130@@ -63,6 +63,37 @@
131 60 represents the number of seconds per datapoint, and 1440 represents the number of datapoints to store. This required some unnecessarily complicated math, so although it's valid, it's not recommended. It's left in so that large organizations with complicated retention rates need not re-write their storage-schemas.conf while when they upgrade.
132
133
134+storage-aggregation.conf
135+------------------------
136+This file defines how to aggregate data to lower-precision retentions. The format is similar to ``storage-schemas.conf``.
137+Important notes before continuing:
138+
139+* This file is optional. If it is not present, defaults will be used.
140+* There is no ``retentions`` line. Instead, there are ``xFilesFactor`` and/or ``aggregationMethod`` lines.
141+* ``xFilesFactor`` should be a floating point number between 0 and 1, and specifies what fraction of the previous retention level's slots must have non-null values in order to aggregate to a non-null value. The default is 0.5.
142+* ``aggregationMethod`` specifies the function used to aggregate values for the next retention level. Legal methods are ``average``, ``sum``, ``min``, ``max``, and ``last``. The default is ``average``.
143+* These are set at the time the first metric is sent.
144+* Changing this file will not affect .wsp files already created on disk. Use whisper-resize.py to change those.
145+
146+Here's an example:
147+
148+.. code-block:: none
149+
150+ [all_min]
151+ pattern = \.min$
152+ xFilesFactor = 0.1
153+ aggregationMethod = min
154+
155+The pattern above will match any metric that ends with ``.min``.
156+
157+The ``xFilesFactor`` line is saying that a minimum of 10% of the slots in the previous retention level must have values for next retention level to contain an aggregate.
158+The ``aggregationMethod`` line is saying that the aggregate function to use is ``min``.
159+
160+If either ``xFilesFactor`` or ``aggregationMethod`` is left out, the default value will be used.
161+
162+The aggregation parameters are kept separate from the retention parameters because the former depends on the type of data being collected and the latter depends on volume and importance.
163+
164+
165 relay-rules.conf
166 ----------------
167 Relay rules are used to send certain metrics to a certain backend. This is handled by the carbon-relay system. It must be running for relaying to work. You can use a regular expression to select the metrics and define the servers to which they should go with the servers line.
168
169=== modified file 'whisper/whisper.py'
170--- whisper/whisper.py 2011-09-11 06:43:44 +0000
171+++ whisper/whisper.py 2011-09-21 08:56:30 +0000
172@@ -246,7 +246,6 @@
173 return aggregationTypeToMethod.get(aggregationType, 'average')
174
175
176-
177 def validateArchiveList(archiveList):
178 """ Validates an archiveList.
179 An ArchiveList must:
180@@ -290,7 +289,7 @@
181 return False
182 return True
183
184-def create(path,archiveList,xFilesFactor=0.5,aggregationMethod='average'):
185+def create(path,archiveList,xFilesFactor=None,aggregationMethod=None):
186 """create(path,archiveList,xFilesFactor=0.5,aggregationMethod='average')
187
188 path is a string
189@@ -298,6 +297,12 @@
190 xFilesFactor specifies the fraction of data points in a propagation interval that must have known values for a propagation to occur
191 aggregationMethod specifies the function to use when propogating data (see ``whisper.aggregationMethods``)
192 """
193+ # Set default params
194+ if xFilesFactor is None:
195+ xFilesFactor = 0.5
196+ if aggregationMethod is None:
197+ aggregationMethod = 'average'
198+
199 #Validate archive configurations...
200 validArchive = validateArchiveList(archiveList)
201 if not validArchive: