Jian Li
Committed by Gerrit Code Review

[ONOS-3536] Implement back-end metrics saving logic using RRD

Change-Id: I1b3c495380884571dc88d2f9fb3152fdf41ef655
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cpman;
17 +
18 +import java.util.Map;
19 +import java.util.concurrent.TimeUnit;
20 +
21 +/**
22 + * Database for storing a metric.
23 + */
24 +public interface MetricsDatabase {
25 + /**
26 + * Returns the metric name of this database.
27 + *
28 + * @return metric name
29 + */
30 + String metricName();
31 +
32 + /**
33 + * Update metric value by specifying metric type.
34 + *
35 + * @param metricType metric type (e.g., load, usage, etc.)
36 + * @param value metric value
37 + */
38 + void updateMetric(String metricType, double value);
39 +
40 + /**
41 + * Update metric value by specifying metric type in a certain time.
42 + *
43 + * @param metricType metric type (e.g., load, usage, etc.)
44 + * @param value metric value
45 + * @param time update time in seconds
46 + */
47 + void updateMetric(String metricType, double value, long time);
48 +
49 + /**
50 + * Update metric values of a collection of metric types.
51 + *
52 + * @param metrics a collection of metrics which consists of a pair of
53 + * metric type and metric value
54 + * @param time update time in seconds
55 + */
56 + void updateMetrics(Map<String, Double> metrics, long time);
57 +
58 + /**
59 + * Update metric values of a collection of metric types.
60 + *
61 + * @param metrics a collection of metrics which consists of a pair of
62 + * metric type and metric value
63 + */
64 + void updateMetrics(Map<String, Double> metrics);
65 +
66 + /**
67 + * Returns most recent metric value of a given metric type.
68 + *
69 + * @param metricType metric type
70 + * @return metric value
71 + */
72 + double recentMetric(String metricType);
73 +
74 + /**
75 + * Return most recent metric values of a given metric type for a given period.
76 + *
77 + * @param metricType metric type
78 + * @param duration duration
79 + * @param unit time unit
80 + * @return a collection of metric value
81 + */
82 + double[] recentMetrics(String metricType, int duration, TimeUnit unit);
83 +
84 + /**
85 + * Returns minimum metric value of a given metric type.
86 + *
87 + * @param metricType metric type
88 + * @return metric value
89 + */
90 + double minMetric(String metricType);
91 +
92 + /**
93 + * Returns maximum metric value of a given metric type.
94 + *
95 + * @param metricType metric type
96 + * @return metric value
97 + */
98 + double maxMetric(String metricType);
99 +
100 + /**
101 + * Returns a collection of metric values of a given metric type for a day.
102 + *
103 + * @param metricType metric type
104 + * @return a collection of metric value
105 + */
106 + double[] metrics(String metricType);
107 +
108 + /**
109 + * Returns a collection of metric values of a given metric type for
110 + * a given period.
111 + *
112 + * @param metricType metric type
113 + * @param startTime start time
114 + * @param endTime end time
115 + * @return a collection of metric value
116 + */
117 + double[] metrics(String metricType, long startTime, long endTime);
118 +
119 + /**
120 + * A builder of MetricsDatabase.
121 + */
122 + interface Builder {
123 +
124 + /**
125 + * Sets the metric name.
126 + *
127 + * @param metricName metric name
128 + * @return builder object
129 + */
130 + Builder withMetricName(String metricName);
131 +
132 + /**
133 + * Add a new metric to be monitored.
134 + *
135 + * @param metricType control metric type
136 + */
137 + Builder addMetricType(String metricType);
138 +
139 + /**
140 + * Builds a MetricDatabase instance.
141 + *
142 + * @return MetricDatabase instance
143 + */
144 + MetricsDatabase build();
145 + }
146 +}
...@@ -105,6 +105,11 @@ ...@@ -105,6 +105,11 @@
105 <version>1.1.1</version> 105 <version>1.1.1</version>
106 </dependency> 106 </dependency>
107 <dependency> 107 <dependency>
108 + <groupId>org.rrd4j</groupId>
109 + <artifactId>rrd4j</artifactId>
110 + <version>2.2</version>
111 + </dependency>
112 + <dependency>
108 <groupId>com.sun.jersey</groupId> 113 <groupId>com.sun.jersey</groupId>
109 <artifactId>jersey-servlet</artifactId> 114 <artifactId>jersey-servlet</artifactId>
110 </dependency> 115 </dependency>
......
...@@ -43,7 +43,6 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService { ...@@ -43,7 +43,6 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService {
43 43
44 @Activate 44 @Activate
45 public void activate() { 45 public void activate() {
46 -
47 } 46 }
48 47
49 @Deactivate 48 @Deactivate
...@@ -53,6 +52,7 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService { ...@@ -53,6 +52,7 @@ public class ControlPlaneMonitor implements ControlPlaneMonitorService {
53 @Override 52 @Override
54 public void updateMetric(ControlMetric cpm, Integer updateInterval, 53 public void updateMetric(ControlMetric cpm, Integer updateInterval,
55 Optional<DeviceId> deviceId) { 54 Optional<DeviceId> deviceId) {
55 +
56 } 56 }
57 57
58 @Override 58 @Override
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cpman.impl;
17 +
18 +import org.apache.commons.lang3.ArrayUtils;
19 +import org.onosproject.cpman.MetricsDatabase;
20 +import org.rrd4j.ConsolFun;
21 +import org.rrd4j.DsType;
22 +import org.rrd4j.core.ArcDef;
23 +import org.rrd4j.core.DsDef;
24 +import org.rrd4j.core.FetchRequest;
25 +import org.rrd4j.core.RrdBackendFactory;
26 +import org.rrd4j.core.RrdDb;
27 +import org.rrd4j.core.RrdDef;
28 +import org.rrd4j.core.Sample;
29 +
30 +import java.io.IOException;
31 +import java.util.ArrayList;
32 +import java.util.Arrays;
33 +import java.util.Collections;
34 +import java.util.List;
35 +import java.util.Map;
36 +import java.util.concurrent.TimeUnit;
37 +import java.util.stream.IntStream;
38 +
39 +import static com.google.common.base.Preconditions.checkArgument;
40 +import static com.google.common.base.Preconditions.checkNotNull;
41 +
42 +/**
43 + * An implementation of control plane metrics back-end database.
44 + */
45 +public final class DefaultMetricsDatabase implements MetricsDatabase {
46 + private String metricName;
47 + private RrdDb rrdDb;
48 + private Sample sample;
49 + private static final long SECONDS_OF_DAY = 60L * 60L * 24L;
50 + private static final long SECONDS_OF_MINUTE = 60L;
51 + private static final ConsolFun CONSOL_FUNCTION = ConsolFun.LAST;
52 + private static final String NON_EXIST_METRIC = "Non-existing metric type.";
53 + private static final String INSUFFICIENT_DURATION = "Given duration less than one minute.";
54 + private static final String EXCEEDED_DURATION = "Given duration exceeds a day time.";
55 +
56 + private DefaultMetricsDatabase(String metricName, RrdDb rrdDb) {
57 + this.metricName = metricName;
58 + this.rrdDb = rrdDb;
59 + }
60 +
61 + @Override
62 + public String metricName() {
63 + return this.metricName;
64 + }
65 +
66 + @Override
67 + public void updateMetric(String metricType, double value) {
68 + updateMetric(metricType, value, System.currentTimeMillis() / 1000L);
69 + }
70 +
71 + @Override
72 + public void updateMetric(String metricType, double value, long time) {
73 + try {
74 + checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
75 + sample = rrdDb.createSample(time);
76 + sample.setValue(metricType, value);
77 + sample.update();
78 + } catch (IOException e) {
79 + e.printStackTrace();
80 + }
81 + }
82 +
83 + @Override
84 + public void updateMetrics(Map<String, Double> metrics) {
85 + updateMetrics(metrics, System.currentTimeMillis() / 1000L);
86 + }
87 +
88 + @Override
89 + public void updateMetrics(Map<String, Double> metrics, long time) {
90 + try {
91 + sample = rrdDb.createSample(time);
92 + metrics.forEach((k, v) -> {
93 + try {
94 + checkArgument(rrdDb.containsDs(k), NON_EXIST_METRIC);
95 + sample.setValue(k, v);
96 + } catch (IOException e) {
97 + e.printStackTrace();
98 + }
99 + });
100 + sample.update();
101 + } catch (IOException e) {
102 + e.printStackTrace();
103 + }
104 + }
105 +
106 + @Override
107 + public double recentMetric(String metricType) {
108 + try {
109 + checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
110 + return rrdDb.getDatasource(metricType).getLastValue();
111 + } catch (IOException e) {
112 + e.printStackTrace();
113 + }
114 + return 0D;
115 + }
116 +
117 + @Override
118 + public double[] recentMetrics(String metricType, int duration, TimeUnit unit) {
119 + try {
120 + checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
121 + long endTime = rrdDb.getLastUpdateTime();
122 + long startTime = endTime - TimeUnit.SECONDS.convert(duration, unit);
123 + if (checkTimeRange(startTime, endTime)) {
124 + FetchRequest fr = rrdDb.createFetchRequest(CONSOL_FUNCTION, startTime, endTime);
125 + return arrangeDataPoints(fr.fetchData().getValues(metricType));
126 + }
127 + } catch (IOException e) {
128 + e.printStackTrace();
129 + }
130 + return new double[0];
131 + }
132 +
133 + @Override
134 + public double minMetric(String metricType) {
135 + try {
136 + checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
137 + long endTime = rrdDb.getLastUpdateTime() - 1;
138 + long startTime = endTime - SECONDS_OF_DAY + 1;
139 + return minMetric(metricType, startTime, endTime);
140 + } catch (IOException e) {
141 + e.printStackTrace();
142 + }
143 + return 0D;
144 + }
145 +
146 + @Override
147 + public double maxMetric(String metricType) {
148 + try {
149 + checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
150 + long endTime = rrdDb.getLastUpdateTime();
151 + long startTime = endTime - SECONDS_OF_DAY;
152 + return maxMetric(metricType, startTime, endTime);
153 + } catch (IOException e) {
154 + e.printStackTrace();
155 + }
156 + return 0D;
157 + }
158 +
159 + @Override
160 + public double[] metrics(String metricType) {
161 + try {
162 + checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
163 + long endTime = rrdDb.getLastUpdateTime();
164 + long startTime = endTime - SECONDS_OF_DAY;
165 + return metrics(metricType, startTime, endTime);
166 + } catch (IOException e) {
167 + e.printStackTrace();
168 + }
169 + return new double[0];
170 + }
171 +
172 + @Override
173 + public double[] metrics(String metricType, long startTime, long endTime) {
174 + try {
175 + checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
176 + if (checkTimeRange(startTime, endTime)) {
177 + FetchRequest fr = rrdDb.createFetchRequest(CONSOL_FUNCTION, startTime, endTime);
178 + return arrangeDataPoints(fr.fetchData().getValues(metricType));
179 + }
180 + } catch (IOException e) {
181 + e.printStackTrace();
182 + }
183 + return new double[0];
184 + }
185 +
186 + // try to check whether projected time range is within a day
187 + private boolean checkTimeRange(long startTime, long endTime) {
188 + // check whether the given startTime and endTime larger than 1 minute
189 + checkArgument(endTime - startTime >= SECONDS_OF_MINUTE, INSUFFICIENT_DURATION);
190 +
191 + // check whether the given start time and endTime smaller than 1 day
192 + checkArgument(endTime - startTime <= SECONDS_OF_DAY, EXCEEDED_DURATION);
193 + return true;
194 + }
195 +
196 + // try to remove first and last data points
197 + private double[] arrangeDataPoints(double[] data) {
198 + return Arrays.copyOfRange(data, 1, data.length - 1);
199 + }
200 +
201 + // obtains maximum metric value among projected range
202 + private double maxMetric(String metricType, long startTime, long endTime) {
203 + double[] all = metrics(metricType, startTime, endTime);
204 + List list = Arrays.asList(ArrayUtils.toObject(all));
205 + return (double) Collections.max(list);
206 + }
207 +
208 + // obtains minimum metric value among projected range
209 + private double minMetric(String metricType, long startTime, long endTime) {
210 + double[] all = metrics(metricType, startTime, endTime);
211 + List list = Arrays.asList(ArrayUtils.toObject(all));
212 + return (double) Collections.min(list);
213 + }
214 +
215 + public static final class Builder implements MetricsDatabase.Builder {
216 + private static final int RESOLUTION = 60; // seconds
217 + private static final String STORING_METHOD = "MEMORY";
218 + private static final DsType SOURCE_TYPE = DsType.GAUGE;
219 + private static final String DB_PATH = "CPMAN";
220 + private static final ConsolFun CONSOL_FUNCTION = ConsolFun.LAST;
221 + private static final double MIN_VALUE = 0;
222 + private static final double MAX_VALUE = Double.NaN;
223 + private static final double XFF_VALUE = 0.2;
224 + private static final int STEP_VALUE = 1;
225 + private static final int ROW_VALUE = 60 * 24;
226 + private static final String METRIC_NAME_MSG = "Must specify a metric name.";
227 + private static final String METRIC_TYPE_MSG = "Must supply at least a metric type.";
228 +
229 + private RrdDb rrdDb;
230 + private RrdDef rrdDef;
231 + private List<DsDef> dsDefs;
232 + private String metricName;
233 +
234 + public Builder() {
235 +
236 + // define the resolution of monitored metrics
237 + rrdDef = new RrdDef(DB_PATH, RESOLUTION);
238 +
239 + // initialize data source definition list
240 + dsDefs = new ArrayList<>();
241 + }
242 +
243 + @Override
244 + public Builder withMetricName(String metricName) {
245 + this.metricName = metricName;
246 + return this;
247 + }
248 +
249 + @Override
250 + public Builder addMetricType(String metricType) {
251 + dsDefs.add(defineSchema(metricType));
252 + return this;
253 + }
254 +
255 + @Override
256 + public MetricsDatabase build() {
257 + checkNotNull(metricName, METRIC_NAME_MSG);
258 + checkArgument(dsDefs.size() != 0, METRIC_TYPE_MSG);
259 +
260 + try {
261 + DsDef[] dsDefArray = new DsDef[dsDefs.size()];
262 + IntStream.range(0, dsDefs.size()).forEach(i -> dsDefArray[i] = dsDefs.get(i));
263 +
264 + rrdDef.addDatasource(dsDefArray);
265 + rrdDef.setStep(RESOLUTION);
266 +
267 + // raw archive, no aggregation is required
268 + ArcDef rawArchive = new ArcDef(CONSOL_FUNCTION, XFF_VALUE,
269 + STEP_VALUE, ROW_VALUE);
270 + rrdDef.addArchive(rawArchive);
271 +
272 + // always store the metric data in memory...
273 + rrdDb = new RrdDb(rrdDef, RrdBackendFactory.getFactory(STORING_METHOD));
274 + } catch (IOException e) {
275 + e.printStackTrace();
276 + }
277 +
278 + return new DefaultMetricsDatabase(metricName, rrdDb);
279 + }
280 +
281 + private DsDef defineSchema(String metricType) {
282 + return new DsDef(metricType, SOURCE_TYPE, RESOLUTION,
283 + MIN_VALUE, MAX_VALUE);
284 + }
285 + }
286 +}
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cpman.impl;
17 +
18 +import org.junit.Before;
19 +import org.junit.Test;
20 +import org.onosproject.cpman.MetricsDatabase;
21 +
22 +import java.util.HashMap;
23 +import java.util.Map;
24 +import java.util.concurrent.TimeUnit;
25 +
26 +import static org.hamcrest.Matchers.is;
27 +import static org.junit.Assert.assertThat;
28 +
29 +/**
30 + * Unit test for control plane metrics back-end database.
31 + */
32 +public class MetricsDatabaseTest {
33 +
34 + private MetricsDatabase mdb;
35 + private static final String CPU_METRIC = "cpu";
36 + private static final String CPU_LOAD = "load";
37 + private static final String MEMORY_METRIC = "memory";
38 + private static final String MEMORY_FREE_PERC = "freePerc";
39 + private static final String MEMORY_USED_PERC = "usedPerc";
40 +
41 + /**
42 + * Initializes the MetricsDatabase instance.
43 + */
44 + @Before
45 + public void setUp() {
46 + mdb = new DefaultMetricsDatabase.Builder()
47 + .withMetricName(CPU_METRIC)
48 + .addMetricType(CPU_LOAD)
49 + .build();
50 + }
51 +
52 + /**
53 + * Tests the metric update function.
54 + */
55 + @Test
56 + public void testMetricUpdate() {
57 + long currentTime = System.currentTimeMillis() / 1000L;
58 +
59 + mdb.updateMetric(CPU_LOAD, 30, currentTime);
60 + assertThat(30D, is(mdb.recentMetric(CPU_LOAD)));
61 +
62 + mdb.updateMetric(CPU_LOAD, 40, currentTime + 60);
63 + assertThat(40D, is(mdb.recentMetric(CPU_LOAD)));
64 +
65 + mdb.updateMetric(CPU_LOAD, 50, currentTime + 120);
66 + assertThat(50D, is(mdb.recentMetric(CPU_LOAD)));
67 + }
68 +
69 + /**
70 + * Tests the metric range fetch function.
71 + */
72 + @Test
73 + public void testMetricRangeFetch() {
74 + // full range fetch
75 + assertThat(mdb.metrics(CPU_LOAD).length, is(60 * 24));
76 +
77 + // query one minute time range
78 + assertThat(mdb.recentMetrics(CPU_LOAD, 1, TimeUnit.MINUTES).length, is(1));
79 +
80 + // query one hour time range
81 + assertThat(mdb.recentMetrics(CPU_LOAD, 1, TimeUnit.HOURS).length, is(60));
82 +
83 + // query one day time range
84 + assertThat(mdb.recentMetrics(CPU_LOAD, 1, TimeUnit.DAYS).length, is(60 * 24));
85 +
86 + // query a specific time range
87 + long endTime = System.currentTimeMillis() / 1000L;
88 + long startTime = endTime - 60 * 5;
89 + assertThat(mdb.metrics(CPU_LOAD, startTime, endTime).length, is(5));
90 + }
91 +
92 + /**
93 + * Test the projected time range.
94 + */
95 + @Test(expected = IllegalArgumentException.class)
96 + public void testExceededTimeRange() {
97 + // query 25 hours time range
98 + assertThat(mdb.recentMetrics(CPU_LOAD, 25, TimeUnit.HOURS).length, is(60 * 24));
99 + }
100 +
101 + /**
102 + * Test the projected time range.
103 + */
104 + @Test(expected = IllegalArgumentException.class)
105 + public void testInsufficientTimeRange() {
106 + // query 50 seconds time range
107 + assertThat(mdb.recentMetrics(CPU_LOAD, 50, TimeUnit.SECONDS).length, is(1));
108 + }
109 +
110 + /**
111 + * Test multiple metrics update and query.
112 + */
113 + @Test
114 + public void testMultipleMetrics() {
115 + MetricsDatabase multiMdb = new DefaultMetricsDatabase.Builder()
116 + .withMetricName(MEMORY_METRIC)
117 + .addMetricType(MEMORY_FREE_PERC)
118 + .addMetricType(MEMORY_USED_PERC)
119 + .build();
120 +
121 + Map<String, Double> metrics = new HashMap<>();
122 + metrics.putIfAbsent(MEMORY_FREE_PERC, 30D);
123 + metrics.putIfAbsent(MEMORY_USED_PERC, 70D);
124 + multiMdb.updateMetrics(metrics);
125 +
126 + assertThat(30D, is(multiMdb.recentMetric(MEMORY_FREE_PERC)));
127 + assertThat(70D, is(multiMdb.recentMetric(MEMORY_USED_PERC)));
128 + }
129 +}