UNPKG

4.61 kBJavaScriptView Raw
1'use strict'
2
3const summary = require('summary')
4const distributions = require('distributions')
5
6function performanceIssue (issue) {
7 return issue ? 'performance' : 'none'
8}
9
10function whiteNoiseSignTest (noise) {
11 // Count the number of sign changes
12 const numSignChanges = nonzero(diff(sign(noise))).length
13
14 // Sign changes on a symmetric distribution will follow a binomial distribution
15 // where the probability of sign change equals 0.5. Thus, perform a binomial
16 // test with the null hypothesis `probability >= 0.5`.
17 // Note that we don't use a two-sided test because:
18 // 1. It is apparently expensive and complicated to implement.
19 // See: https://github.com/scipy/scipy/blob/master/scipy/stats/morestats.py
20 // look for `binom_test`
21 // 2. We currently have don't understand of what a consistent sign change
22 // indicates. Thus we will treat `probability > 0.5` as being fine.
23 const binomial = new distributions.Binomial(0.5, noise.length)
24 const pvalue = binomial.cdf(numSignChanges)
25
26 return pvalue
27}
28
29// Implementation of the mira skrewness tests
30// Paper:
31// Title: Distribution-free test for symmetry based on Bonferroni's Measure
32// Link: https://www.researchgate.net/publication/2783935
33function miraSkewnessTest (handleStat, handles) {
34 const c = 0.5 // Distribution dependent constant, 0.5 is the best value for unknown distribution
35 const n = handleStat.size()
36 const median = handleStat.median()
37
38 // Compute the bonferroni measure
39 const bonferroni = 2 * (handleStat.mean() - median)
40
41 // Compute the variance for the sqrt(n) * bonferroni measure
42 const medianAbsoluteDeviation = summary(handles.map((x) => Math.abs(x - median))).mean()
43 const densityInverse = (Math.pow(n, 1 / 5) / (2 * c)) * (
44 handleStat.quartile(1 / 2 + 0.5 * Math.pow(n, -1 / 5)) -
45 handleStat.quartile(1 / 2 - 0.5 * Math.pow(n, -1 / 5) + 1 / n)
46 )
47 const bonferroniVariance = 4 * handleStat.variance() +
48 densityInverse * densityInverse -
49 4 * densityInverse * medianAbsoluteDeviation
50
51 // Compute Z statistics
52 const Z = Math.sqrt(n) * bonferroni / Math.sqrt(bonferroniVariance)
53
54 // Compute two-sided p-value
55 const normal = new distributions.Normal()
56 const pvalue = 2 * (1 - normal.cdf(Math.abs(Z)))
57
58 return pvalue
59}
60
61function analyseHandles (systemInfo, processStatSubset, traceEventSubset) {
62 // Healthy handle graphs tends to grow or shrink in small steps.
63 //
64 // handles
65 // | ^ ^ /^^
66 // | / \ / \ /\ / \
67 // | / \^/ \/ \
68 // ------------------------ time
69 //
70 // Unhealthy handles graphs increase and makes large drop (sawtooth pattern)
71 //
72 // handles
73 // | ^^^ /^^^ /^^^
74 // | / | / | /
75 // | / |^^ |^^^
76 // ------------------------ time
77 //
78 // A heuristic statistical description of such a behaviour, is that
79 // healthy handle graphs are essentially random walks on a symmetric
80 // distribution.
81 // To test this, perform a sign change test on the differential.
82 const handles = processStatSubset.map((d) => d.handles)
83 const handleStat = summary(handles)
84 if (handles.length < 2) {
85 return 'data'
86 }
87
88 // Check for stochasticity, otherwise the following is mathematical nonsense.
89 if (handleStat.sd() === 0) {
90 return 'none'
91 }
92
93 // 1. Assuming handles is a random-walk process, then calculate the first
94 // order auto-diffrential to get the white-noise.
95 // 2. Reduce the dataset to those values where the value did change, this
96 // is to avoid over-sampling and ignore constant-periods.
97 // NOTE: since sd() !== 0, noise is guaranteed to have some data
98 const noise = nonzero(diff(handles))
99
100 // Perform a sign test on the assumed-noise.
101 // This is to test the sawtooth pattern.
102 const signAlpha = 1e-7 // risk acceptance
103 const signPvalue = whiteNoiseSignTest(noise)
104 const signIssueDetected = signPvalue < signAlpha
105
106 // Perform a two-sided mira-skewness-test.
107 // This is to test an increasing function, that eventually flattens out
108 const miraAlpha = 1e-10 // risk acceptance
109 const miraPvalue = miraSkewnessTest(handleStat, handles)
110 const miraIssueDetected = miraPvalue < miraAlpha
111
112 return performanceIssue(signIssueDetected || miraIssueDetected)
113}
114
115module.exports = analyseHandles
116
117function diff (vec) {
118 const changes = []
119 let last = vec[0]
120 for (let i = 1; i < vec.length; i++) {
121 changes.push(vec[i] - last)
122 last = vec[i]
123 }
124 return changes
125}
126
127function sign (vec) {
128 return vec.map(Math.sign)
129}
130
131function nonzero (vec) {
132 return vec.filter((v) => v !== 0)
133}