1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2012-2019 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
6
7# Simple statistical functions
8
9import math
10
11def to_std_unit(v, unit=1000):
12    if v>=unit**3:
13        return "G", v//(unit**3)
14    if v>=unit**2:
15        return "M", v//(unit**2)
16    if v>=unit:
17        return "K", v//unit
18    return "", v
19
20def std_unit(v, unit=1000) -> str:
21    unit, value = to_std_unit(v, unit)
22    return "%s%s" % (int(value), unit)
23
24def std_unit_dec(v):
25    unit, value = to_std_unit(v*10.0)
26    if value>=100:
27        return "%s%s" % (int(value//10), unit)
28    if int(value)%10==0:
29        return "%s%s" % (int(value/10), unit)
30    return "%s%s" % (int(value)/10.0, unit)
31
32
33def absolute_to_diff_values(in_data):
34    """ Given a list of values, return a new list
35        containing the incremental diff between each value
36        ie: [0,2,2,10] -> [2,0,8]
37    """
38    last_value = None
39    data = []
40    for x in in_data:
41        if last_value is not None:
42            data.append(x-last_value)
43        last_value = x
44    return data
45
46def values_to_scaled_values(data, scale_unit=10, min_scaled_value=10, num_values=20):
47    #print("values_to_scaled_values(%s, %s, %s)" % (data, scale_unit, num_values))
48    if not data:
49        return 0, data
50    max_v = max(data)
51    #pad with None values so we have at least num_values:
52    if len(data)<num_values:
53        if isinstance(data, tuple):
54            data = list(data)
55        for _ in range(num_values-len(data)):
56            data.insert(0, None)
57    scale = 1
58    assert scale_unit>1
59    while scale*scale_unit*min_scaled_value<=max_v:
60        scale *= scale_unit
61    if scale==1:
62        return scale, data
63    sdata = []
64    for x in data:
65        if x is None:
66            sdata.append(None)
67        else:
68            sdata.append(x/scale)
69    return scale, sdata
70
71def values_to_diff_scaled_values(data, scale_unit=10, min_scaled_value=10, num_values=20):
72    return values_to_scaled_values(absolute_to_diff_values(data), scale_unit=scale_unit, min_scaled_value=min_scaled_value, num_values=num_values)
73
74def get_weighted_list_stats(weighted_values, show_percentile=False):
75    values = tuple(x[0] for x in weighted_values)
76    if not values:
77        return {}
78    #weighted mean:
79    tw = 0
80    tv = 0
81    for v, w in weighted_values:
82        tw += w
83        tv += v * w
84    avg = tv/tw
85    stats = {
86             "min"   : int(min(values)),
87             "max"   : int(max(values)),
88             "avg"   : int(avg),
89             }
90    if show_percentile:
91        #percentile
92        svalues = sorted(values)
93        for i in range(1,10):
94            pct = i*10
95            index = len(values)*i//10
96            stats["%ip" % pct] = int(svalues[index])
97    return stats
98
99
100def find_invpow(x, n):
101    """Finds the integer component of the n'th root of x,
102    an integer such that y ** n <= x < (y + 1) ** n.
103    """
104    high = 1
105    while high ** n < x:
106        high *= 2
107    low = high/2
108    while low < high:
109        mid = (low + high) // 2
110        if low < mid and mid**n < x:
111            low = mid
112        elif high > mid and mid**n > x:
113            high = mid
114        else:
115            return mid
116    return mid + 1
117
118def get_list_stats(in_values, show_percentile=(5, 8, 9), show_dev=False):
119    #this may be backed by a deque/list whichi is used by other threads
120    #so make a copy before use:
121    values = tuple(in_values)
122    if not values:
123        return  {}
124    #arithmetic mean
125    avg = sum(values)/len(values)
126    lstats = {
127              "cur"       : int(values[-1]),
128              "min"       : int(min(values)),
129              "max"       : int(max(values)),
130              "avg"       : int(avg),
131              }
132    if show_dev:
133        p = 1           #geometric mean
134        h = 0           #harmonic mean
135        var = 0         #variance
136        counter = 0
137        for x in values:
138            if x!=0:
139                p *= x
140                h += 1.0/x
141                counter += 1
142            var += (x-avg)**2
143        #standard deviation:
144        std = math.sqrt(var/len(values))
145        lstats["std"] = int(std)
146        if avg!=0:
147            #coefficient of variation
148            lstats["cv_pct"] = int(100.0*std/avg)
149        if counter>0 and p<float('inf'):
150            #geometric mean
151            try:
152                v = int(math.pow(p, 1.0/counter))
153            except OverflowError:
154                v = find_invpow(p, counter)
155            lstats["gm"] = v
156        if h!=0:
157            #harmonic mean
158            lstats["h"] = int(counter/h)
159    if show_percentile:
160        #percentile
161        svalues = sorted(values)
162        for i in show_percentile:
163            pct = i*10
164            index = len(values)*i//10
165            lstats["%ip" % pct] = int(svalues[index])
166    return lstats
167