UNPKG

11.7 kBJavaScriptView Raw
1(function (window) {
2 'use strict';
3
4 /**
5 * @description
6 *
7 * This object provides a client for the Statful service to register
8 * application metrics. It can be called as follows:
9 *
10 * statful.initialize({
11 * app: 'example-app'
12 * namespace: 'mobile',
13 * dryrun: false,
14 * debug: false
15 * });
16 *
17 * statful.counter('metricName', 1);
18 *
19 */
20 var defaultConfig = {
21 dryrun: false,
22 debug: false,
23 app: undefined,
24 namespace: 'web',
25 tags: {},
26 aggregations: [],
27 aggregationFrequency: 10,
28 timer: {
29 tags: {
30 unit: 'ms'
31 },
32 aggregations: ['avg', 'p90', 'count']
33 },
34 counter: {
35 tags: {},
36 aggregations: ['sum', 'count']
37 },
38 gauge: {
39 tags: {},
40 aggregations: ['last']
41 },
42 timeout: 2000,
43 flushInterval: 10000,
44 sampleRate: 100
45 };
46
47 var logger;
48
49 var statful = {
50 config: {
51 apiAddress: 'https://beacon.statful.com'
52 },
53 endpoints: {
54 metrics: 'beacon/metrics'
55 },
56 perf: window.performance,
57
58 /**
59 * Initialize the Statful client settings and register events
60 */
61 initialize: function (clientConfig) {
62 var self = this;
63
64 //Private functions
65 self.mergeConfigs = function (clientConfig) {
66 if (typeof clientConfig !== 'object' || clientConfig === null) {
67 clientConfig = {};
68 }
69
70 // Set default properties
71 Object.keys(defaultConfig).forEach(function (key) {
72 self.config[key] = defaultConfig[key];
73 });
74
75 Object.keys(clientConfig).forEach(function (key) {
76 self.config[key] = clientConfig[key];
77 });
78 };
79
80 self.metricsData = function (name, type, value, tags, aggregations, aggregationFrequency, namespace, sampleRate) {
81 return {
82 name: name,
83 type: type,
84 value: value,
85 tags: self.util.setTags(tags || {}, self.config.tags, self.config[type].tags, self.config.app),
86 aggregations: self.util.setAggregations(aggregations, self.config.aggregations, self.config[type].aggregations),
87 aggregationFrequency: self.util.setAggregationFrequency(aggregationFrequency, self.config.aggregationFrequency, self.config[type].aggregationFrequency),
88 namespace: namespace || self.config.namespace,
89 sampleRate: sampleRate || self.config.sampleRate
90 };
91 };
92
93 this.mergeConfigs(clientConfig);
94
95 // Create Logger
96 logger = new StatfulLogger(self.config.debug);
97
98 // Create Util
99 self.util = new StatfulUtil({
100 apiAddress: this.config.apiAddress,
101 debug: this.config.debug,
102 dryrun: this.config.dryrun,
103 flushInterval: this.config.flushInterval,
104 timeout: this.config.timeout
105 });
106
107 //Register queue to send metrics
108 self.util.registerQueue('metrics', this.endpoints.metrics, this.config.flushInterval);
109 },
110
111 /**
112 * Measure a timer using the user timing specification
113 * @param {string} measureName name of the measure to create
114 * @returns {number}
115 */
116 measureTimeUserTiming: function (measureName) {
117 var time;
118 var measure = statful.perf.getEntriesByName(measureName).filter(function filterMeasures(entry) {
119 return entry.entryType === 'measure';
120 });
121
122 if (measure.length > 0) {
123 // Always use the most recent measure if more exist
124 time = measure[measure.length - 1].duration;
125 } else {
126 logger.debug('Measure ' + measureName + ' not found');
127 }
128
129 return time;
130 },
131
132 /**
133 * Clear marks
134 * @param {Array} marks - list of marks to clear (optional)
135 */
136 clearMarks: function (marks) {
137 try {
138 if (marks) {
139 marks.forEach(function (mark) {
140 if (mark) {
141 statful.perf.clearMarks(mark);
142 }
143 });
144 } else {
145 statful.perf.clearMarks();
146 }
147 } catch (ex) {
148 logger.error(ex);
149 }
150 },
151
152 /**
153 * Clear resources
154 */
155 clearResources: function () {
156 try {
157 statful.perf.clearResourceTimings();
158 } catch (ex) {
159 logger.error(ex);
160 }
161 },
162
163 /**
164 * Clear measures
165 * @param {Array} measures - list of measures to clear (optional)
166 */
167 clearMeasures: function (measures) {
168 try {
169 if (measures) {
170 measures.forEach(function (measure) {
171 statful.perf.clearMeasures(measure);
172 });
173 } else {
174 statful.perf.clearMeasures();
175 }
176 } catch (ex) {
177 logger.error(ex);
178 }
179 },
180
181 /**
182 * Register a mark using the user timing specification
183 * @param markName - name of the mark to add
184 */
185 registerMark: function (markName) {
186 try {
187 logger.debug('Register Mark', markName);
188 if (markName) {
189 statful.perf.mark(markName);
190 } else {
191 logger.error('Undefined resource name to register as a mark');
192 }
193 } catch (ex) {
194 logger.error(ex);
195 }
196 },
197
198 /**
199 * Register a measure and sends a timer using the user timing specification and metric options
200 * @param {string} measureName - name of the measure to create in the browser (ie. timeto.apploaded)
201 * @param {string} metricName - name of the metric to send to statful (ie. timeto)
202 * @param {object} options - set of option (clearMarks, clearMeasures, startMark, endMark, tags and aggregations)
203 */
204 registerMeasure: function (measureName, metricName, options) {
205 try {
206 logger.debug('Register Measure', measureName, metricName, options);
207 if (measureName) {
208 var defaults = {
209 clearMarks: false,
210 clearMeasures: false
211 };
212
213 options = options || {};
214
215
216 Object.keys(options).forEach(function (key) {
217 defaults[key] = options[key];
218 });
219
220 // Create endMark if none is set
221 if (!defaults.endMark) {
222 this.registerMark(measureName);
223 defaults.endMark = measureName;
224 }
225
226 statful.perf.measure(measureName, defaults.startMark, defaults.endMark);
227
228 // Measure timer
229 var time = this.measureTimeUserTiming(measureName);
230
231 if (time) {
232 // Push metrics to queue
233 this.util.addItemToQueue('metrics', new this.metricsData(metricName, 'timer', time,
234 defaults.tags, defaults.aggregations, defaults.aggregationFrequency, defaults.namespace, defaults.sampleRate));
235 } else {
236 logger.error('Failed to get measure time to register as timer value');
237 }
238
239 if (defaults.clearMarks) {
240 this.clearMarks([defaults.startMark, defaults.endMark]);
241 }
242
243 if (defaults.clearMeasures) {
244 this.clearMeasures([measureName]);
245 }
246 } else {
247 logger.error('Undefined resource name to register as a measure');
248 }
249 } catch (ex) {
250 logger.error(ex);
251 }
252 },
253
254 /**
255 * Register timer
256 * @param {string} metricName - metric name to be used as metric name
257 * @param {number} metricValue - timer value to be sent
258 * @param {object} options - set of option (tags, agg, aggFreq, namespace)
259 */
260 timer: function (metricName, metricValue, options) {
261 try {
262 logger.debug('Register Timer', metricName, metricValue, options);
263 if (metricName && metricValue >= 0) {
264 options = options || {};
265
266 // Push metrics to queue
267 var item = new this.metricsData(metricName, 'timer', metricValue, options.tags, options.agg, options.aggFreq, options.namespace, options.sampleRate);
268 this.util.addItemToQueue('metrics', item);
269 } else {
270 logger.error('Undefined metric name or invalid value to register as a timer');
271 }
272 } catch (ex) {
273 logger.error(ex);
274 }
275 },
276
277 /**
278 * Register counter
279 * @param {string} metricName - metric name to be sent
280 * @param {number} metricValue - count value to be sent
281 * @param {object} options - set of option (tags, agg, aggFreq, namespace)
282 */
283 counter: function (metricName, metricValue, options) {
284 try {
285 logger.debug('Register Counter', metricName, options);
286 metricValue = metricValue || 1;
287
288 if (metricName) {
289 options = options || {};
290
291 // Push metrics to queue
292 var item = new this.metricsData(metricName, 'counter', metricValue, options.tags, options.agg, options.aggFreq, options.namespace, options.sampleRate);
293 this.util.addItemToQueue('metrics', item);
294 } else {
295 logger.error('Undefined metric name to register as a counter');
296 }
297 } catch (ex) {
298 logger.error(ex);
299 }
300 },
301
302 /**
303 * Register gauge
304 * @param {string} metricName metric name to be sent
305 * @param {number} metricValue gauge value to be sent
306 * @param {object} options - set of option (tags, agg, aggFreq, namespace)
307 */
308 gauge: function (metricName, metricValue, options) {
309 try {
310 logger.debug('Register Gauge', metricName, metricValue, options);
311 if (metricName && metricValue) {
312 options = options || {};
313
314 // Push metrics to queue
315 var item = new this.metricsData(metricName, 'gauge', metricValue, options.tags, options.agg, options.aggFreq, options.namespace, options.sampleRate);
316 this.util.addItemToQueue('metrics', item);
317 } else {
318 logger.error('Undefined metric name/value to register as a gauge');
319 }
320 } catch (ex) {
321 logger.error(ex);
322 }
323 }
324 };
325
326 window.statful = statful;
327
328})(window);