blob: fd88072669341b08b5146003890645a23a2ddc25 [file] [log] [blame]
Jian Li46148902016-01-29 13:33:50 -08001/*
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 */
16package org.onosproject.cpman.impl;
17
18import org.apache.commons.lang3.ArrayUtils;
19import org.onosproject.cpman.MetricsDatabase;
20import org.rrd4j.ConsolFun;
21import org.rrd4j.DsType;
22import org.rrd4j.core.ArcDef;
23import org.rrd4j.core.DsDef;
24import org.rrd4j.core.FetchRequest;
25import org.rrd4j.core.RrdBackendFactory;
26import org.rrd4j.core.RrdDb;
27import org.rrd4j.core.RrdDef;
28import org.rrd4j.core.Sample;
Jian Lidaf55ea2016-04-04 20:38:30 -070029import org.slf4j.Logger;
30import org.slf4j.LoggerFactory;
Jian Li46148902016-01-29 13:33:50 -080031
32import java.io.IOException;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.Collections;
36import java.util.List;
37import java.util.Map;
38import java.util.concurrent.TimeUnit;
39import java.util.stream.IntStream;
40
41import static com.google.common.base.Preconditions.checkArgument;
42import static com.google.common.base.Preconditions.checkNotNull;
43
44/**
45 * An implementation of control plane metrics back-end database.
46 */
47public final class DefaultMetricsDatabase implements MetricsDatabase {
Jian Lidaf55ea2016-04-04 20:38:30 -070048 private final Logger log = LoggerFactory.getLogger(getClass());
49
Jian Li46148902016-01-29 13:33:50 -080050 private String metricName;
Jian Lidaf55ea2016-04-04 20:38:30 -070051 private String resourceName;
Jian Li46148902016-01-29 13:33:50 -080052 private RrdDb rrdDb;
53 private Sample sample;
54 private static final long SECONDS_OF_DAY = 60L * 60L * 24L;
55 private static final long SECONDS_OF_MINUTE = 60L;
56 private static final ConsolFun CONSOL_FUNCTION = ConsolFun.LAST;
57 private static final String NON_EXIST_METRIC = "Non-existing metric type.";
58 private static final String INSUFFICIENT_DURATION = "Given duration less than one minute.";
59 private static final String EXCEEDED_DURATION = "Given duration exceeds a day time.";
60
Jian Lic5cb4a12016-02-03 23:24:42 -080061 /**
62 * Constructs a metrics database using the given metric name and
63 * round robin database.
64 *
65 * @param metricName metric name
66 * @param rrdDb round robin database
67 */
Jian Lidaf55ea2016-04-04 20:38:30 -070068 private DefaultMetricsDatabase(String metricName, String resourceName, RrdDb rrdDb) {
Jian Li46148902016-01-29 13:33:50 -080069 this.metricName = metricName;
Jian Lidaf55ea2016-04-04 20:38:30 -070070 this.resourceName = resourceName;
Jian Li46148902016-01-29 13:33:50 -080071 this.rrdDb = rrdDb;
72 }
73
74 @Override
75 public String metricName() {
76 return this.metricName;
77 }
78
79 @Override
Jian Lidaf55ea2016-04-04 20:38:30 -070080 public String resourceName() {
81 return this.resourceName;
82 }
83
84 @Override
Jian Li46148902016-01-29 13:33:50 -080085 public void updateMetric(String metricType, double value) {
86 updateMetric(metricType, value, System.currentTimeMillis() / 1000L);
87 }
88
89 @Override
90 public void updateMetric(String metricType, double value, long time) {
91 try {
92 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
93 sample = rrdDb.createSample(time);
94 sample.setValue(metricType, value);
95 sample.update();
96 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -070097 log.error("Failed to update metric value due to {}", e.getMessage());
Jian Li46148902016-01-29 13:33:50 -080098 }
99 }
100
101 @Override
102 public void updateMetrics(Map<String, Double> metrics) {
103 updateMetrics(metrics, System.currentTimeMillis() / 1000L);
104 }
105
106 @Override
107 public void updateMetrics(Map<String, Double> metrics, long time) {
108 try {
109 sample = rrdDb.createSample(time);
110 metrics.forEach((k, v) -> {
111 try {
112 checkArgument(rrdDb.containsDs(k), NON_EXIST_METRIC);
113 sample.setValue(k, v);
114 } catch (IOException e) {
115 e.printStackTrace();
116 }
117 });
118 sample.update();
119 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700120 log.error("Failed to update metric values due to {}", e.getMessage());
Jian Li46148902016-01-29 13:33:50 -0800121 }
122 }
123
124 @Override
125 public double recentMetric(String metricType) {
126 try {
127 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
128 return rrdDb.getDatasource(metricType).getLastValue();
129 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700130 log.error("Failed to obtain metric value due to {}", e.getMessage());
131 return 0D;
Jian Li46148902016-01-29 13:33:50 -0800132 }
Jian Li46148902016-01-29 13:33:50 -0800133 }
134
135 @Override
136 public double[] recentMetrics(String metricType, int duration, TimeUnit unit) {
137 try {
138 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
139 long endTime = rrdDb.getLastUpdateTime();
140 long startTime = endTime - TimeUnit.SECONDS.convert(duration, unit);
141 if (checkTimeRange(startTime, endTime)) {
142 FetchRequest fr = rrdDb.createFetchRequest(CONSOL_FUNCTION, startTime, endTime);
143 return arrangeDataPoints(fr.fetchData().getValues(metricType));
Jian Lidaf55ea2016-04-04 20:38:30 -0700144 } else {
145 log.warn("Data projection is out-of-range");
146 return new double[0];
Jian Li46148902016-01-29 13:33:50 -0800147 }
148 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700149 log.error("Failed to obtain metric values due to {}", e.getMessage());
150 return new double[0];
Jian Li46148902016-01-29 13:33:50 -0800151 }
Jian Li46148902016-01-29 13:33:50 -0800152 }
153
154 @Override
155 public double minMetric(String metricType) {
156 try {
157 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
158 long endTime = rrdDb.getLastUpdateTime() - 1;
159 long startTime = endTime - SECONDS_OF_DAY + 1;
160 return minMetric(metricType, startTime, endTime);
161 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700162 log.error("Failed to obtain metric value due to {}", e.getMessage());
163 return 0D;
Jian Li46148902016-01-29 13:33:50 -0800164 }
Jian Li46148902016-01-29 13:33:50 -0800165 }
166
167 @Override
168 public double maxMetric(String metricType) {
169 try {
170 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
171 long endTime = rrdDb.getLastUpdateTime();
172 long startTime = endTime - SECONDS_OF_DAY;
173 return maxMetric(metricType, startTime, endTime);
174 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700175 log.error("Failed to obtain metric value due to {}", e.getMessage());
176 return 0D;
Jian Li46148902016-01-29 13:33:50 -0800177 }
Jian Li46148902016-01-29 13:33:50 -0800178 }
179
180 @Override
181 public double[] metrics(String metricType) {
182 try {
183 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
184 long endTime = rrdDb.getLastUpdateTime();
185 long startTime = endTime - SECONDS_OF_DAY;
186 return metrics(metricType, startTime, endTime);
187 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700188 log.error("Failed to obtain metric values due to {}", e.getMessage());
189 return new double[0];
Jian Li46148902016-01-29 13:33:50 -0800190 }
Jian Li46148902016-01-29 13:33:50 -0800191 }
192
193 @Override
194 public double[] metrics(String metricType, long startTime, long endTime) {
195 try {
196 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
197 if (checkTimeRange(startTime, endTime)) {
198 FetchRequest fr = rrdDb.createFetchRequest(CONSOL_FUNCTION, startTime, endTime);
199 return arrangeDataPoints(fr.fetchData().getValues(metricType));
Jian Lidaf55ea2016-04-04 20:38:30 -0700200 } else {
201 log.warn("Data projection is out-of-range");
202 return new double[0];
Jian Li46148902016-01-29 13:33:50 -0800203 }
204 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700205 log.error("Failed to obtain metric values due to {}", e.getMessage());
206 return new double[0];
Jian Li46148902016-01-29 13:33:50 -0800207 }
Jian Li46148902016-01-29 13:33:50 -0800208 }
209
Jian Liec343ff2016-02-02 17:11:13 -0800210 @Override
211 public long lastUpdate(String metricType) {
212 try {
213 checkArgument(rrdDb.containsDs(metricType), NON_EXIST_METRIC);
Jian Lidaf55ea2016-04-04 20:38:30 -0700214 return rrdDb.getLastUpdateTime();
Jian Liec343ff2016-02-02 17:11:13 -0800215 } catch (IOException e) {
Jian Lidaf55ea2016-04-04 20:38:30 -0700216 log.error("Failed to obtain last update time due to {}", e.getMessage());
217 return 0L;
Jian Liec343ff2016-02-02 17:11:13 -0800218 }
Jian Liec343ff2016-02-02 17:11:13 -0800219 }
220
Jian Li46148902016-01-29 13:33:50 -0800221 // try to check whether projected time range is within a day
222 private boolean checkTimeRange(long startTime, long endTime) {
223 // check whether the given startTime and endTime larger than 1 minute
224 checkArgument(endTime - startTime >= SECONDS_OF_MINUTE, INSUFFICIENT_DURATION);
225
226 // check whether the given start time and endTime smaller than 1 day
227 checkArgument(endTime - startTime <= SECONDS_OF_DAY, EXCEEDED_DURATION);
228 return true;
229 }
230
231 // try to remove first and last data points
232 private double[] arrangeDataPoints(double[] data) {
233 return Arrays.copyOfRange(data, 1, data.length - 1);
234 }
235
236 // obtains maximum metric value among projected range
237 private double maxMetric(String metricType, long startTime, long endTime) {
238 double[] all = metrics(metricType, startTime, endTime);
239 List list = Arrays.asList(ArrayUtils.toObject(all));
240 return (double) Collections.max(list);
241 }
242
243 // obtains minimum metric value among projected range
244 private double minMetric(String metricType, long startTime, long endTime) {
245 double[] all = metrics(metricType, startTime, endTime);
246 List list = Arrays.asList(ArrayUtils.toObject(all));
247 return (double) Collections.min(list);
248 }
249
250 public static final class Builder implements MetricsDatabase.Builder {
Jian Li85060ac2016-02-04 09:58:56 -0800251 private static final int RESOLUTION_IN_SECOND = 60;
Jian Li46148902016-01-29 13:33:50 -0800252 private static final String STORING_METHOD = "MEMORY";
253 private static final DsType SOURCE_TYPE = DsType.GAUGE;
254 private static final String DB_PATH = "CPMAN";
255 private static final ConsolFun CONSOL_FUNCTION = ConsolFun.LAST;
256 private static final double MIN_VALUE = 0;
257 private static final double MAX_VALUE = Double.NaN;
258 private static final double XFF_VALUE = 0.2;
259 private static final int STEP_VALUE = 1;
260 private static final int ROW_VALUE = 60 * 24;
261 private static final String METRIC_NAME_MSG = "Must specify a metric name.";
Jian Lidaf55ea2016-04-04 20:38:30 -0700262 private static final String RESOURCE_NAME_MSG = "Must specify a resource name.";
Jian Li46148902016-01-29 13:33:50 -0800263 private static final String METRIC_TYPE_MSG = "Must supply at least a metric type.";
Jian Lidaf55ea2016-04-04 20:38:30 -0700264 private static final String SPLITTER = "_";
Jian Li46148902016-01-29 13:33:50 -0800265
266 private RrdDb rrdDb;
267 private RrdDef rrdDef;
268 private List<DsDef> dsDefs;
269 private String metricName;
Jian Lidaf55ea2016-04-04 20:38:30 -0700270 private String resourceName;
Jian Li46148902016-01-29 13:33:50 -0800271
272 public Builder() {
Jian Li46148902016-01-29 13:33:50 -0800273 // initialize data source definition list
274 dsDefs = new ArrayList<>();
275 }
276
277 @Override
Jian Lidaf55ea2016-04-04 20:38:30 -0700278 public Builder withMetricName(String metric) {
279 this.metricName = metric;
280 return this;
281 }
Jian Li7d180c52016-02-01 21:53:08 -0800282
Jian Lidaf55ea2016-04-04 20:38:30 -0700283 @Override
284 public MetricsDatabase.Builder withResourceName(String resource) {
285 this.resourceName = resource;
Jian Li46148902016-01-29 13:33:50 -0800286 return this;
287 }
288
289 @Override
290 public Builder addMetricType(String metricType) {
291 dsDefs.add(defineSchema(metricType));
292 return this;
293 }
294
295 @Override
296 public MetricsDatabase build() {
297 checkNotNull(metricName, METRIC_NAME_MSG);
Jian Lidaf55ea2016-04-04 20:38:30 -0700298 checkNotNull(resourceName, RESOURCE_NAME_MSG);
Jian Li46148902016-01-29 13:33:50 -0800299 checkArgument(dsDefs.size() != 0, METRIC_TYPE_MSG);
300
Jian Lidaf55ea2016-04-04 20:38:30 -0700301 // define the resolution of monitored metrics
302 rrdDef = new RrdDef(DB_PATH + SPLITTER + metricName +
303 SPLITTER + resourceName, RESOLUTION_IN_SECOND);
304
Jian Li46148902016-01-29 13:33:50 -0800305 try {
306 DsDef[] dsDefArray = new DsDef[dsDefs.size()];
307 IntStream.range(0, dsDefs.size()).forEach(i -> dsDefArray[i] = dsDefs.get(i));
308
309 rrdDef.addDatasource(dsDefArray);
Jian Li85060ac2016-02-04 09:58:56 -0800310 rrdDef.setStep(RESOLUTION_IN_SECOND);
Jian Li46148902016-01-29 13:33:50 -0800311
312 // raw archive, no aggregation is required
313 ArcDef rawArchive = new ArcDef(CONSOL_FUNCTION, XFF_VALUE,
314 STEP_VALUE, ROW_VALUE);
315 rrdDef.addArchive(rawArchive);
316
317 // always store the metric data in memory...
318 rrdDb = new RrdDb(rrdDef, RrdBackendFactory.getFactory(STORING_METHOD));
319 } catch (IOException e) {
320 e.printStackTrace();
321 }
322
Jian Lidaf55ea2016-04-04 20:38:30 -0700323 return new DefaultMetricsDatabase(metricName, resourceName, rrdDb);
Jian Li46148902016-01-29 13:33:50 -0800324 }
325
326 private DsDef defineSchema(String metricType) {
Jian Li85060ac2016-02-04 09:58:56 -0800327 return new DsDef(metricType, SOURCE_TYPE, RESOLUTION_IN_SECOND,
Jian Li46148902016-01-29 13:33:50 -0800328 MIN_VALUE, MAX_VALUE);
329 }
330 }
331}