UNPKG

3.21 kBJavaScriptView Raw
1'use strict'
2
3const summary = require('summary')
4const distributions = require('distributions')
5
6function analyseHandles (processStatSubset, traceEventSubset) {
7 // Healthy handle graphs tends to grow or shrink in small steps.
8 //
9 // handles
10 // | ^ ^ /^^
11 // | / \ / \ /\ / \
12 // | / \^/ \/ \
13 // ------------------------ time
14 //
15 // Unhealthy handles graphs increase and makes large drop
16 //
17 // handles
18 // | ^^^ /^^^ /^^^
19 // | / | / | /
20 // | / |^^ |^^^
21 // ------------------------ time
22 //
23 // A heuristic statistical description of such a behaviour, is that
24 // healthy handle graphs are essentially random walks on a symmetric
25 // distribution.
26 // To test this, perform a sign change test on the differential.
27 const handles = processStatSubset.map((d) => d.handles)
28
29 // Check for stochasticity, otherwise the following is mathematical nonsense.
30 if (summary(handles).sd() === 0) {
31 return false
32 }
33
34 // Calculate the changes in handles (differential)
35 const changes = diff(handles)
36 // Determine if the change was an increase or a decrease
37 const direction = changes.map(Math.sign)
38 const notConstant = direction.map((v) => v !== 0)
39 // Count the number of sign changes
40 const signChanges = diff(direction).map((v) => v !== 0)
41 const numSignChanges = summary(signChanges).sum()
42
43 // In cases where the number of handles is somewhat constant, it looks
44 // like there are unusually few sign changes. But this is actually fine if
45 // the server doesn't do anything asynchronously at all. To not see this
46 // as a handle issue, compare not the number of sign changes with
47 // `processStatSubset.length` but instead with the number of observations
48 // where changes were observed.
49 // There can be an off-by-one error were there are more sign changes than
50 // non constant observations. Simply round the number of non constant
51 // observations up to fit.
52 const numNotConstant = Math.max(numSignChanges, summary(notConstant).sum())
53
54 // Sign changes on a symmetric distribution will follow a binomial distribution
55 // where the probability of sign change equals 0.5. Thus, perform a binomial
56 // test with the null hypothesis `probability >= 0.5`.
57 // Note that we don't use a two-sided test because:
58 // 1. It is apparently expensive and complicated to implement.
59 // See: https://github.com/scipy/scipy/blob/master/scipy/stats/morestats.py
60 // look for `binom_test`
61 // 2. We currently have don't understand of what a consistent sign change
62 // indicates. Thus we will treat `probability > 0.5` as being fine.
63 const binomial = new distributions.Binomial(0.5, numNotConstant)
64 const pvalue = binomial.cdf(numSignChanges)
65
66 // If is is very unlikely that the sign change probability is greater than
67 // 0.5, then we likely have an issue with the handles.
68 const alpha = 0.001 // risk acceptance
69 return pvalue < alpha
70}
71
72module.exports = analyseHandles
73
74function diff (handles) {
75 const changes = []
76 let lastHandles = handles[0]
77 for (let i = 1; i < handles.length; i++) {
78 changes.push(handles[i] - lastHandles)
79 lastHandles = handles[i]
80 }
81 return changes
82}