blob: 5c8835543a3c1ce249a6c72f8d994c5c16d524c2 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001import {assert} from 'chai';
2import sinon from 'sinon';
3
4import ClientLogger from './client-logger.js';
5import MonorailTSMon from './monorail-ts-mon.js';
6
7describe('ClientLogger', () => {
8 const startedKey = 'ClientLogger.rutabaga.started';
9 let c;
10
11 beforeEach(() => {
12 window.CS_env = {
13 token: 'rutabaga-token',
14 tokenExpiresSec: 1234,
15 app_version: 'rutabaga-version',
16 };
17 window.chops = {rpc: {PrpcClient: sinon.spy()}};
18 window.ga = sinon.spy();
19 MonorailTSMon.prototype.disableAfterNextFlush = sinon.spy();
20 c = new ClientLogger('rutabaga');
21 });
22
23 afterEach(() => {
24 sessionStorage.clear();
25 });
26
27 describe('constructor', () => {
28 it('assigns this.category', () => {
29 assert.equal(c.category, 'rutabaga');
30 });
31
32 it('gets started events from sessionStorage', () => {
33 const startedEvents = {
34 event1: {
35 time: 12345678,
36 labels: ['label1', 'label2'],
37 },
38 event2: {
39 time: 87654321,
40 labels: ['label2'],
41 },
42 };
43 sessionStorage[startedKey] = JSON.stringify(startedEvents);
44
45 c = new ClientLogger('rutabaga');
46 assert.deepEqual(startedEvents, c.startedEvents);
47 });
48 });
49
50 describe('records ts_mon metrics', () => {
51 let issueCreateMetric;
52 let issueUpdateMetric;
53 let autocompleteMetric;
54 let c;
55
56 beforeEach(() => {
57 window.ga = sinon.spy();
58 c = new ClientLogger('issues');
59 issueCreateMetric = c.tsMon._userTimingMetrics[0].metric;
60 issueCreateMetric.add = sinon.spy();
61
62 issueUpdateMetric = c.tsMon._userTimingMetrics[1].metric;
63 issueUpdateMetric.add = sinon.spy();
64
65 autocompleteMetric = c.tsMon._userTimingMetrics[2].metric;
66 autocompleteMetric.add = sinon.spy();
67 });
68
69 it('bogus', () => {
70 c.logStart('rutabaga');
71 c.logEnd('rutabaga');
72 sinon.assert.notCalled(issueCreateMetric.add);
73 sinon.assert.notCalled(issueUpdateMetric.add);
74 sinon.assert.notCalled(autocompleteMetric.add);
75 });
76
77 it('new-issue', () => {
78 c.logStart('new-issue', 'server-time');
79 c.logEnd('new-issue', 'server-time');
80 sinon.assert.notCalled(issueUpdateMetric.add);
81 sinon.assert.notCalled(autocompleteMetric.add);
82
83 sinon.assert.calledOnce(issueCreateMetric.add);
84 assert.isNumber(issueCreateMetric.add.getCall(0).args[0]);
85 assert.isString(issueCreateMetric.add.getCall(0).args[1].get('client_id'));
86 assert.equal(issueCreateMetric.add.getCall(0).args[1].get('host_name'),
87 'rutabaga-version');
88 });
89
90 it('issue-update', () => {
91 c.logStart('issue-update', 'computer-time');
92 c.logEnd('issue-update', 'computer-time');
93 sinon.assert.notCalled(issueCreateMetric.add);
94 sinon.assert.notCalled(autocompleteMetric.add);
95
96 sinon.assert.calledOnce(issueUpdateMetric.add);
97 assert.isNumber(issueUpdateMetric.add.getCall(0).args[0]);
98 assert.isString(issueUpdateMetric.add.getCall(0).args[1].get('client_id'));
99 assert.equal(issueUpdateMetric.add.getCall(0).args[1].get('host_name'),
100 'rutabaga-version');
101 });
102
103 it('populate-options', () => {
104 c.logStart('populate-options');
105 c.logEnd('populate-options');
106 sinon.assert.notCalled(issueCreateMetric.add);
107 sinon.assert.notCalled(issueUpdateMetric.add);
108 // Autocomplete is not called in issues category.
109 sinon.assert.notCalled(autocompleteMetric.add);
110
111 c = new ClientLogger('autocomplete');
112 autocompleteMetric = c.tsMon._userTimingMetrics[2].metric;
113 autocompleteMetric.add = sinon.spy();
114
115 c.logStart('populate-options', 'user-time');
116 c.logEnd('populate-options', 'user-time');
117 sinon.assert.notCalled(issueCreateMetric.add);
118 sinon.assert.notCalled(issueUpdateMetric.add);
119
120 sinon.assert.calledOnce(autocompleteMetric.add);
121 assert.isNumber(autocompleteMetric.add.getCall(0).args[0]);
122 assert.isString(autocompleteMetric.add.getCall(0).args[1].get('client_id'));
123 assert.equal(autocompleteMetric.add.getCall(0).args[1].get('host_name'),
124 'rutabaga-version');
125 });
126 });
127
128 describe('logStart', () => {
129 let c;
130 let clock;
131 const currentTime = 5000;
132
133 beforeEach(() => {
134 c = new ClientLogger('rutabaga');
135 clock = sinon.useFakeTimers(currentTime);
136 });
137
138 afterEach(() => {
139 clock.restore();
140 sessionStorage.clear();
141 });
142
143 it('creates a new startedEvent if none', () => {
144 c.logStart('event-name', 'event-label');
145
146 sinon.assert.calledOnce(ga);
147 sinon.assert.calledWith(ga, 'send', 'event', 'rutabaga',
148 'event-name-start', 'event-label');
149
150 const expectedStartedEvents = {
151 'event-name': {
152 time: currentTime,
153 labels: {
154 'event-label': currentTime,
155 },
156 },
157 };
158 assert.deepEqual(c.startedEvents, expectedStartedEvents);
159 assert.deepEqual(JSON.parse(sessionStorage[startedKey]),
160 expectedStartedEvents);
161 });
162
163 it('uses an existing startedEvent', () => {
164 c.startedEvents['event-name'] = {
165 time: 1234,
166 labels: {
167 'event-label': 1000,
168 },
169 };
170 c.logStart('event-name', 'event-label');
171
172 sinon.assert.calledOnce(ga);
173 sinon.assert.calledWith(ga, 'send', 'event', 'rutabaga',
174 'event-name-start', 'event-label');
175
176 // TODO(jeffcarp): Audit is this wanted behavior? Replacing event time
177 // but not label time?
178 const expectedStartedEvents = {
179 'event-name': {
180 time: 1234,
181 labels: {
182 'event-label': currentTime,
183 },
184 },
185 };
186 assert.deepEqual(c.startedEvents, expectedStartedEvents);
187 assert.deepEqual(JSON.parse(sessionStorage[startedKey]),
188 expectedStartedEvents);
189 });
190 });
191
192 describe('logPause', () => {
193 const startTime = 1234;
194 const currentTime = 5000;
195 let c;
196 let clock;
197
198 beforeEach(() => {
199 clock = sinon.useFakeTimers(currentTime);
200 c = new ClientLogger('rutabaga');
201 c.startedEvents['event-name'] = {
202 time: startTime,
203 labels: {
204 'event-label': startTime,
205 },
206 };
207 });
208
209 afterEach(() => {
210 clock.restore();
211 sessionStorage.clear();
212 });
213
214 it('throws if no label given', () => {
215 assert.throws(() => {
216 c.logPause('bogus');
217 }, 'event with no label');
218 });
219
220 it('exits early if no start event exists', () => {
221 const originalStartedEvents = Object.assign(c.startedEvents, {});
222 c.logPause('bogus', 'fogus');
223 assert.deepEqual(c.startedEvents, originalStartedEvents);
224 });
225
226 it('exits early if no label exists', () => {
227 const originalStartedEvents = Object.assign(c.startedEvents, {});
228 c.logPause('event-name', 'fogus');
229 assert.deepEqual(c.startedEvents, originalStartedEvents);
230 });
231
232 it('adds elapsed time to start event', () => {
233 c.logPause('event-name', 'event-label');
234
235 const expectedStartedEvents = {
236 'event-name': {
237 time: startTime,
238 labels: {
239 'event-label': startTime,
240 },
241 elapsed: {
242 'event-label': currentTime - startTime,
243 },
244 },
245 };
246 assert.deepEqual(c.startedEvents, expectedStartedEvents);
247 assert.deepEqual(
248 JSON.parse(sessionStorage['ClientLogger.rutabaga.started']),
249 expectedStartedEvents);
250 });
251 });
252
253 describe('logResume', () => {
254 let c;
255 let clock;
256 const startTimeEvent = 1234;
257 const startTimeLabel = 2345;
258 const labelElapsed = 4321;
259 const currentTime = 6000;
260
261 beforeEach(() => {
262 clock = sinon.useFakeTimers(currentTime);
263 c = new ClientLogger('rutabaga');
264 c.startedEvents['event-name'] = {
265 time: startTimeEvent,
266 labels: {
267 'event-label': startTimeLabel,
268 },
269 elapsed: {
270 'event-label': labelElapsed,
271 },
272 };
273 });
274
275 afterEach(() => {
276 clock.restore();
277 sessionStorage.clear();
278 });
279
280 it('throws if no label given', () => {
281 assert.throws(() => {
282 c.logResume('bogus');
283 }, 'no label');
284 });
285
286 it('exits early if no start event exists', () => {
287 const originalStartedEvents = Object.assign(c.startedEvents, {});
288 c.logResume('bogus', 'fogus');
289 assert.deepEqual(c.startedEvents, originalStartedEvents);
290 });
291
292 it('exits early if the label was never paused', () => {
293 c.startedEvents['event-name'] = {
294 time: startTimeEvent,
295 labels: {
296 'event-label': startTimeLabel,
297 },
298 elapsed: {},
299 };
300
301 const originalStartedEvents = Object.assign(c.startedEvents, {});
302 c.logResume('event-name', 'event-label');
303 assert.deepEqual(c.startedEvents, originalStartedEvents);
304 });
305
306 it('sets start event time to current time', () => {
307 c.logResume('event-name', 'event-label');
308
309 const expectedStartedEvents = {
310 'event-name': {
311 time: startTimeEvent,
312 labels: {
313 'event-label': currentTime,
314 },
315 elapsed: {
316 'event-label': labelElapsed,
317 },
318 },
319 };
320 assert.deepEqual(c.startedEvents, expectedStartedEvents);
321 assert.deepEqual(
322 JSON.parse(sessionStorage['ClientLogger.rutabaga.started']),
323 expectedStartedEvents);
324 });
325 });
326
327 describe('logEnd', () => {
328 let c;
329 let clock;
330 const startTimeEvent = 1234;
331 const startTimeLabel1 = 2345;
332 const startTimeLabel2 = 3456;
333 const currentTime = 10000;
334
335 beforeEach(() => {
336 c = new ClientLogger('rutabaga');
337 clock = sinon.useFakeTimers(currentTime);
338 c.tsMon.recordUserTiming = sinon.spy();
339 c.startedEvents = {
340 someEvent: {
341 time: startTimeEvent,
342 labels: {
343 label1: startTimeLabel1,
344 label2: startTimeLabel2,
345 },
346 },
347 };
348 });
349
350 afterEach(() => {
351 clock.restore();
352 });
353
354 it('returns early if no event was started', () => {
355 c.startedEvents = {someEvent: {}};
356 const originalStartedEvents = Object.assign(c.startedEvents, {});
357 c.logEnd('bogus');
358 sinon.assert.notCalled(window.ga);
359 assert.isNull(sessionStorage.getItem(startedKey));
360 assert.deepEqual(c.startedEvents, originalStartedEvents);
361 });
362
363 it('returns early if label was not started', () => {
364 c.startedEvents = {someEvent: {labels: {}}};
365 const originalStartedEvents = Object.assign(c.startedEvents, {});
366 c.logEnd('someEvent', 'bogus');
367 sinon.assert.notCalled(window.ga);
368 assert.isNull(sessionStorage.getItem(startedKey));
369 assert.deepEqual(c.startedEvents, originalStartedEvents);
370 });
371
372 it('does not log non-labeled events over threshold', () => {
373 c.startedEvents = {someEvent: {time: currentTime - 1000}};
374 c.logEnd('someEvent', null, 999);
375
376 sinon.assert.calledOnce(window.ga);
377 sinon.assert.calledWith(window.ga, 'send', 'event', 'rutabaga',
378 'someEvent-end', null, undefined);
379 sinon.assert.notCalled(c.tsMon.recordUserTiming);
380 assert.equal(sessionStorage.getItem(startedKey), '{}');
381 });
382
383 it('does not log labeled events over threshold', () => {
384 const elapsedLabel2 = 2000;
385 c.startedEvents.someEvent.elapsed = {
386 label1: currentTime - 1000,
387 label2: elapsedLabel2,
388 };
389 c.logEnd('someEvent', 'label1', 999);
390
391 sinon.assert.calledOnce(window.ga);
392 sinon.assert.calledWith(window.ga, 'send', 'event', 'rutabaga',
393 'someEvent-end', 'label1', undefined);
394 // TODO(jeffcarp): Feature: add GA event if over threshold.
395 sinon.assert.notCalled(c.tsMon.recordUserTiming);
396
397 const expectedStartedEvents = {
398 someEvent: {
399 time: startTimeEvent,
400 labels: {
401 label2: startTimeLabel2,
402 },
403 elapsed: {
404 label2: elapsedLabel2,
405 },
406 },
407 };
408 assert.deepEqual(c.startedEvents, expectedStartedEvents);
409 assert.deepEqual(JSON.parse(sessionStorage[startedKey]),
410 expectedStartedEvents);
411 });
412
413 it('calls ga() with timing and event info for all labels', () => {
414 const label1Elapsed = 1000;
415 const label2Elapsed = 2500;
416 c.startedEvents.someEvent.elapsed = {
417 label1: label1Elapsed,
418 label2: label2Elapsed,
419 };
420 c.logEnd('someEvent');
421
422 assert.deepEqual(ga.getCall(0).args, [
423 'send', 'timing', {
424 timingCategory: 'rutabaga',
425 timingValue: currentTime - startTimeEvent,
426 timingVar: 'someEvent',
427 }]);
428
429 assert.deepEqual(ga.getCall(1).args, [
430 'send', 'timing', {
431 timingCategory: 'rutabaga',
432 timingValue: (currentTime - startTimeLabel1) + label1Elapsed,
433 timingVar: 'someEvent',
434 timingLabel: 'label1',
435 }]);
436 assert.deepEqual(ga.getCall(2).args, [
437 'send', 'timing', {
438 timingCategory: 'rutabaga',
439 timingValue: (currentTime - startTimeLabel2) + label2Elapsed,
440 timingVar: 'someEvent',
441 timingLabel: 'label2',
442 }]);
443 assert.deepEqual(ga.getCall(3).args, [
444 'send', 'event', 'rutabaga', 'someEvent-end', undefined, undefined,
445 ]);
446 assert.deepEqual(c.tsMon.recordUserTiming.getCall(0).args, [
447 'rutabaga', 'someEvent', null, currentTime - startTimeEvent,
448 ]);
449 assert.deepEqual(c.tsMon.recordUserTiming.getCall(1).args, [
450 'rutabaga', 'someEvent', 'label1',
451 (currentTime - startTimeLabel1) + label1Elapsed,
452 ]);
453 assert.deepEqual(c.tsMon.recordUserTiming.getCall(2).args, [
454 'rutabaga', 'someEvent', 'label2',
455 (currentTime - startTimeLabel2) + label2Elapsed,
456 ]);
457
458 assert.deepEqual(c.startedEvents, {});
459 assert.equal(sessionStorage.getItem(startedKey), '{}');
460 });
461
462 it('calling with a label calls ga() only for that label', () => {
463 const label1Elapsed = 1000;
464 const label2Elapsed = 2500;
465 c.startedEvents.someEvent.elapsed = {
466 label1: label1Elapsed,
467 label2: label2Elapsed,
468 };
469 c.logEnd('someEvent', 'label2');
470
471 assert.deepEqual(ga.getCall(0).args, [
472 'send', 'timing', {
473 timingCategory: 'rutabaga',
474 timingValue: (currentTime - startTimeLabel2) + label2Elapsed,
475 timingVar: 'someEvent',
476 timingLabel: 'label2',
477 }]);
478 assert.deepEqual(window.ga.getCall(1).args, [
479 'send', 'event', 'rutabaga', 'someEvent-end', 'label2', undefined,
480 ]);
481 sinon.assert.calledOnce(c.tsMon.recordUserTiming);
482 sinon.assert.calledWith(c.tsMon.recordUserTiming, 'rutabaga',
483 'someEvent', 'label2', (currentTime - startTimeLabel2) + label2Elapsed);
484
485 const expectedStartedEvents = {
486 someEvent: {
487 time: startTimeEvent,
488 labels: {
489 label1: startTimeLabel1,
490 },
491 elapsed: {
492 label1: label1Elapsed,
493 },
494 },
495 };
496 assert.deepEqual(c.startedEvents, expectedStartedEvents);
497 assert.deepEqual(JSON.parse(sessionStorage[startedKey]),
498 expectedStartedEvents);
499 });
500
501 it('calling logStart, logPause, logResume, and logEnd works for labels',
502 () => {
503 let countedElapsedTime = 0;
504 c.logStart('someEvent', 'label1');
505 clock.tick(1000);
506 countedElapsedTime += 1000;
507 c.logPause('someEvent', 'label1');
508 clock.tick(1000);
509 c.logResume('someEvent', 'label1');
510 clock.tick(1000);
511 countedElapsedTime += 1000;
512 c.logEnd('someEvent', 'label1');
513
514 assert.deepEqual(ga.getCall(0).args, [
515 'send', 'event', 'rutabaga', 'someEvent-start', 'label1', undefined,
516 ]);
517 assert.deepEqual(ga.getCall(1).args, [
518 'send', 'timing', {
519 timingCategory: 'rutabaga',
520 timingValue: countedElapsedTime,
521 timingVar: 'someEvent',
522 timingLabel: 'label1',
523 }]);
524 assert.deepEqual(window.ga.getCall(2).args, [
525 'send', 'event', 'rutabaga', 'someEvent-end', 'label1', undefined,
526 ]);
527 sinon.assert.calledOnce(c.tsMon.recordUserTiming);
528 sinon.assert.calledWith(c.tsMon.recordUserTiming, 'rutabaga',
529 'someEvent', 'label1', countedElapsedTime);
530
531 const expectedStartedEvents = {
532 someEvent: {
533 time: startTimeEvent,
534 labels: {
535 label2: startTimeLabel2,
536 },
537 elapsed: {},
538 },
539 };
540 assert.deepEqual(c.startedEvents, expectedStartedEvents);
541 assert.deepEqual(JSON.parse(sessionStorage[startedKey]),
542 expectedStartedEvents);
543 });
544
545 it('logs some events when others are above threshold', () => {
546 c.startedEvents = {
547 someEvent: {
548 time: 9500,
549 labels: {
550 overThresholdWithoutElapsed: 8000,
551 overThresholdWithElapsed: 9500,
552 underThresholdWithoutElapsed: 9750,
553 underThresholdWithElapsed: 9650,
554 exactlyOnThresholdWithoutElapsed: 9001,
555 exactlyOnThresholdWithElapsed: 9002,
556 },
557 elapsed: {
558 overThresholdWithElapsed: 1000,
559 underThresholdWithElapsed: 100,
560 exactlyOnThresholdWithElapsed: 1,
561 },
562 },
563 };
564 c.logEnd('someEvent', null, 999);
565
566 // Verify ga() calls.
567 assert.equal(window.ga.getCalls().length, 6);
568 assert.deepEqual(ga.getCall(0).args, [
569 'send', 'timing', {
570 timingCategory: 'rutabaga',
571 timingValue: 500,
572 timingVar: 'someEvent',
573 }]);
574 assert.deepEqual(ga.getCall(1).args, [
575 'send', 'timing', {
576 timingCategory: 'rutabaga',
577 timingValue: 250,
578 timingVar: 'someEvent',
579 timingLabel: 'underThresholdWithoutElapsed',
580 }]);
581 assert.deepEqual(ga.getCall(2).args, [
582 'send', 'timing', {
583 timingCategory: 'rutabaga',
584 timingValue: 450,
585 timingVar: 'someEvent',
586 timingLabel: 'underThresholdWithElapsed',
587 }]);
588 assert.deepEqual(ga.getCall(3).args, [
589 'send', 'timing', {
590 timingCategory: 'rutabaga',
591 timingValue: 999,
592 timingVar: 'someEvent',
593 timingLabel: 'exactlyOnThresholdWithoutElapsed',
594 }]);
595 assert.deepEqual(ga.getCall(4).args, [
596 'send', 'timing', {
597 timingCategory: 'rutabaga',
598 timingValue: 999,
599 timingVar: 'someEvent',
600 timingLabel: 'exactlyOnThresholdWithElapsed',
601 }]);
602 assert.deepEqual(ga.getCall(5).args, [
603 'send', 'event', 'rutabaga', 'someEvent-end', null, undefined,
604 ]);
605
606 // Verify ts_mon.recordUserTiming() calls.
607 assert.equal(c.tsMon.recordUserTiming.getCalls().length, 5);
608 assert.deepEqual(c.tsMon.recordUserTiming.getCall(0).args, [
609 'rutabaga', 'someEvent', null, 500,
610 ]);
611 assert.deepEqual(c.tsMon.recordUserTiming.getCall(1).args, [
612 'rutabaga', 'someEvent', 'underThresholdWithoutElapsed', 250,
613 ]);
614 assert.deepEqual(c.tsMon.recordUserTiming.getCall(2).args, [
615 'rutabaga', 'someEvent', 'underThresholdWithElapsed', 450,
616 ]);
617 assert.deepEqual(c.tsMon.recordUserTiming.getCall(3).args, [
618 'rutabaga', 'someEvent', 'exactlyOnThresholdWithoutElapsed', 999,
619 ]);
620 assert.deepEqual(c.tsMon.recordUserTiming.getCall(4).args, [
621 'rutabaga', 'someEvent', 'exactlyOnThresholdWithElapsed', 999,
622 ]);
623 assert.deepEqual(c.startedEvents, {});
624 assert.deepEqual(JSON.parse(sessionStorage[startedKey]), {});
625 });
626 });
627});