13bf7e27ac
util/stats/db.py: need to import the values function util/stats/info.py: it's just run --HG-- extra : convert_revision : 3cb67d8112a1a5fdf761b73732859a71f585bd1f
716 lines
21 KiB
Python
716 lines
21 KiB
Python
# Copyright (c) 2003-2004 The Regents of The University of Michigan
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are
|
|
# met: redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer;
|
|
# redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution;
|
|
# neither the name of the copyright holders nor the names of its
|
|
# contributors may be used to endorse or promote products derived from
|
|
# this software without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
from __future__ import division
|
|
import operator, re, types
|
|
|
|
def unproxy(proxy):
|
|
if hasattr(proxy, '__unproxy__'):
|
|
return proxy.__unproxy__()
|
|
|
|
return proxy
|
|
|
|
def scalar(stat):
|
|
stat = unproxy(stat)
|
|
assert(stat.__scalar__() != stat.__vector__())
|
|
return stat.__scalar__()
|
|
|
|
def vector(stat):
|
|
stat = unproxy(stat)
|
|
assert(stat.__scalar__() != stat.__vector__())
|
|
return stat.__vector__()
|
|
|
|
def value(stat, *args):
|
|
stat = unproxy(stat)
|
|
return stat.__value__(*args)
|
|
|
|
def values(stat, run):
|
|
stat = unproxy(stat)
|
|
result = []
|
|
for i in xrange(len(stat)):
|
|
val = value(stat, run, i)
|
|
if val is None:
|
|
return None
|
|
result.append(val)
|
|
return result
|
|
|
|
def total(stat, run):
|
|
return sum(values(stat, run))
|
|
|
|
def len(stat):
|
|
stat = unproxy(stat)
|
|
return stat.__len__()
|
|
|
|
class Value(object):
|
|
def __scalar__(self):
|
|
raise AttributeError, "must define __scalar__ for %s" % (type (self))
|
|
def __vector__(self):
|
|
raise AttributeError, "must define __vector__ for %s" % (type (self))
|
|
|
|
def __add__(self, other):
|
|
return BinaryProxy(operator.__add__, self, other)
|
|
def __sub__(self, other):
|
|
return BinaryProxy(operator.__sub__, self, other)
|
|
def __mul__(self, other):
|
|
return BinaryProxy(operator.__mul__, self, other)
|
|
def __div__(self, other):
|
|
return BinaryProxy(operator.__div__, self, other)
|
|
def __truediv__(self, other):
|
|
return BinaryProxy(operator.__truediv__, self, other)
|
|
def __floordiv__(self, other):
|
|
return BinaryProxy(operator.__floordiv__, self, other)
|
|
|
|
def __radd__(self, other):
|
|
return BinaryProxy(operator.__add__, other, self)
|
|
def __rsub__(self, other):
|
|
return BinaryProxy(operator.__sub__, other, self)
|
|
def __rmul__(self, other):
|
|
return BinaryProxy(operator.__mul__, other, self)
|
|
def __rdiv__(self, other):
|
|
return BinaryProxy(operator.__div__, other, self)
|
|
def __rtruediv__(self, other):
|
|
return BinaryProxy(operator.__truediv__, other, self)
|
|
def __rfloordiv__(self, other):
|
|
return BinaryProxy(operator.__floordiv__, other, self)
|
|
|
|
def __neg__(self):
|
|
return UnaryProxy(operator.__neg__, self)
|
|
def __pos__(self):
|
|
return UnaryProxy(operator.__pos__, self)
|
|
def __abs__(self):
|
|
return UnaryProxy(operator.__abs__, self)
|
|
|
|
class Scalar(Value):
|
|
def __scalar__(self):
|
|
return True
|
|
|
|
def __vector__(self):
|
|
return False
|
|
|
|
def __value__(self, run):
|
|
raise AttributeError, '__value__ must be defined'
|
|
|
|
class VectorItemProxy(Value):
|
|
def __init__(self, proxy, index):
|
|
self.proxy = proxy
|
|
self.index = index
|
|
|
|
def __scalar__(self):
|
|
return True
|
|
|
|
def __vector__(self):
|
|
return False
|
|
|
|
def __value__(self, run):
|
|
return value(self.proxy, run, self.index)
|
|
|
|
class Vector(Value):
|
|
def __scalar__(self):
|
|
return False
|
|
|
|
def __vector__(self):
|
|
return True
|
|
|
|
def __value__(self, run, index):
|
|
raise AttributeError, '__value__ must be defined'
|
|
|
|
def __getitem__(self, index):
|
|
return VectorItemProxy(self, index)
|
|
|
|
class ScalarConstant(Scalar):
|
|
def __init__(self, constant):
|
|
self.constant = constant
|
|
def __value__(self, run):
|
|
return self.constant
|
|
|
|
class VectorConstant(Vector):
|
|
def __init__(self, constant):
|
|
self.constant = constant
|
|
def __value__(self, run, index):
|
|
return self.constant[index]
|
|
def __len__(self):
|
|
return len(self.constant)
|
|
|
|
def WrapValue(value):
|
|
if isinstance(value, (int, long, float)):
|
|
return ScalarConstant(value)
|
|
if isinstance(value, (list, tuple)):
|
|
return VectorConstant(value)
|
|
if isinstance(value, Value):
|
|
return value
|
|
|
|
raise AttributeError, 'Only values can be wrapped'
|
|
|
|
class Statistic(object):
|
|
def __getattr__(self, attr):
|
|
if attr in ('data', 'x', 'y'):
|
|
result = self.source.data(self, self.bins, self.ticks)
|
|
self.data = result.data
|
|
self.x = result.x
|
|
self.y = result.y
|
|
return super(Statistic, self).__getattribute__(attr)
|
|
|
|
def __setattr__(self, attr, value):
|
|
if attr == 'stat':
|
|
raise AttributeError, '%s is read only' % stat
|
|
if attr in ('source', 'bins', 'ticks'):
|
|
if getattr(self, attr) != value:
|
|
if hasattr(self, 'data'):
|
|
delattr(self, 'data')
|
|
|
|
super(Statistic, self).__setattr__(attr, value)
|
|
|
|
class ValueProxy(Value):
|
|
def __getattr__(self, attr):
|
|
if attr == '__value__':
|
|
if scalar(self):
|
|
return self.__scalarvalue__
|
|
if vector(self):
|
|
return self.__vectorvalue__
|
|
if attr == '__len__':
|
|
if vector(self):
|
|
return self.__vectorlen__
|
|
return super(ValueProxy, self).__getattribute__(attr)
|
|
|
|
class UnaryProxy(ValueProxy):
|
|
def __init__(self, op, arg):
|
|
self.op = op
|
|
self.arg = WrapValue(arg)
|
|
|
|
def __scalar__(self):
|
|
return scalar(self.arg)
|
|
|
|
def __vector__(self):
|
|
return vector(self.arg)
|
|
|
|
def __scalarvalue__(self, run):
|
|
val = value(self.arg, run)
|
|
if val is None:
|
|
return None
|
|
return self.op(val)
|
|
|
|
def __vectorvalue__(self, run, index):
|
|
val = value(self.arg, run, index)
|
|
if val is None:
|
|
return None
|
|
return self.op(val)
|
|
|
|
def __vectorlen__(self):
|
|
return len(unproxy(self.arg))
|
|
|
|
class BinaryProxy(ValueProxy):
|
|
def __init__(self, op, arg0, arg1):
|
|
super(BinaryProxy, self).__init__()
|
|
self.op = op
|
|
self.arg0 = WrapValue(arg0)
|
|
self.arg1 = WrapValue(arg1)
|
|
|
|
def __scalar__(self):
|
|
return scalar(self.arg0) and scalar(self.arg1)
|
|
|
|
def __vector__(self):
|
|
return vector(self.arg0) or vector(self.arg1)
|
|
|
|
def __scalarvalue__(self, run):
|
|
val0 = value(self.arg0, run)
|
|
val1 = value(self.arg1, run)
|
|
if val0 is None or val1 is None:
|
|
return None
|
|
return self.op(val0, val1)
|
|
|
|
def __vectorvalue__(self, run, index):
|
|
if scalar(self.arg0):
|
|
val0 = value(self.arg0, run)
|
|
if vector(self.arg0):
|
|
val0 = value(self.arg0, run, index)
|
|
if scalar(self.arg1):
|
|
val1 = value(self.arg1, run)
|
|
if vector(self.arg1):
|
|
val1 = value(self.arg1, run, index)
|
|
|
|
if val0 is None or val1 is None:
|
|
return None
|
|
|
|
return self.op(val0, val1)
|
|
|
|
def __vectorlen__(self):
|
|
if vector(self.arg0) and scalar(self.arg1):
|
|
return len(self.arg0)
|
|
if scalar(self.arg0) and vector(self.arg1):
|
|
return len(self.arg1)
|
|
|
|
len0 = len(self.arg0)
|
|
len1 = len(self.arg1)
|
|
|
|
if len0 != len1:
|
|
raise AttributeError, \
|
|
"vectors of different lengths %d != %d" % (len0, len1)
|
|
|
|
return len0
|
|
|
|
class Proxy(Value):
|
|
def __init__(self, name, dict):
|
|
self.name = name
|
|
self.dict = dict
|
|
|
|
def __unproxy__(self):
|
|
return unproxy(self.dict[self.name])
|
|
|
|
def __getitem__(self, index):
|
|
return ItemProxy(self, index)
|
|
|
|
def __getattr__(self, attr):
|
|
return AttrProxy(self, attr)
|
|
|
|
class ItemProxy(Proxy):
|
|
def __init__(self, proxy, index):
|
|
self.proxy = proxy
|
|
self.index = index
|
|
|
|
def __unproxy__(self):
|
|
return unproxy(unproxy(self.proxy)[self.index])
|
|
|
|
class AttrProxy(Proxy):
|
|
def __init__(self, proxy, attr):
|
|
self.proxy = proxy
|
|
self.attr = attr
|
|
|
|
def __unproxy__(self):
|
|
return unproxy(getattr(unproxy(self.proxy), self.attr))
|
|
|
|
class ProxyGroup(object):
|
|
def __init__(self, dict=None, **kwargs):
|
|
self.__dict__['dict'] = {}
|
|
|
|
if dict is not None:
|
|
self.dict.update(dict)
|
|
|
|
if kwargs:
|
|
self.dict.update(kwargs)
|
|
|
|
def __getattr__(self, name):
|
|
return Proxy(name, self.dict)
|
|
|
|
def __setattr__(self, attr, value):
|
|
self.dict[attr] = value
|
|
|
|
class ScalarStat(Statistic,Scalar):
|
|
def __value__(self, run):
|
|
if run not in self.data:
|
|
return None
|
|
return self.data[run][0][0]
|
|
|
|
def display(self, run=None):
|
|
import display
|
|
p = display.Print()
|
|
p.name = self.name
|
|
p.desc = self.desc
|
|
p.value = value(self, run)
|
|
p.flags = self.flags
|
|
p.precision = self.precision
|
|
if display.all or (self.flags & flags.printable):
|
|
p.display()
|
|
|
|
class VectorStat(Statistic,Vector):
|
|
def __value__(self, run, item):
|
|
if run not in self.data:
|
|
return None
|
|
return self.data[run][item][0]
|
|
|
|
def __len__(self):
|
|
return self.x
|
|
|
|
def display(self, run=None):
|
|
import display
|
|
d = display.VectorDisplay()
|
|
d.name = self.name
|
|
d.desc = self.desc
|
|
d.value = [ value(self, run, i) for i in xrange(len(self)) ]
|
|
d.flags = self.flags
|
|
d.precision = self.precision
|
|
d.display()
|
|
|
|
class Formula(Value):
|
|
def __getattribute__(self, attr):
|
|
if attr not in ( '__scalar__', '__vector__', '__value__', '__len__' ):
|
|
return super(Formula, self).__getattribute__(attr)
|
|
|
|
formula = re.sub(':', '__', self.formula)
|
|
value = eval(formula, self.source.stattop)
|
|
return getattr(value, attr)
|
|
|
|
class SimpleDist(Statistic):
|
|
def __init__(self, sums, squares, samples):
|
|
self.sums = sums
|
|
self.squares = squares
|
|
self.samples = samples
|
|
|
|
def display(self, name, desc, flags, precision):
|
|
import display
|
|
p = display.Print()
|
|
p.flags = flags
|
|
p.precision = precision
|
|
|
|
if self.samples > 0:
|
|
p.name = name + ".mean"
|
|
p.value = self.sums / self.samples
|
|
p.display()
|
|
|
|
p.name = name + ".stdev"
|
|
if self.samples > 1:
|
|
var = (self.samples * self.squares - self.sums ** 2) \
|
|
/ (self.samples * (self.samples - 1))
|
|
if var >= 0:
|
|
p.value = math.sqrt(var)
|
|
else:
|
|
p.value = 'NaN'
|
|
else:
|
|
p.value = 0.0
|
|
p.display()
|
|
|
|
p.name = name + ".samples"
|
|
p.value = self.samples
|
|
p.display()
|
|
|
|
def comparable(self, other):
|
|
return True
|
|
|
|
def __eq__(self, other):
|
|
return self.sums == other.sums and self.squares == other.squares and \
|
|
self.samples == other.samples
|
|
|
|
def __isub__(self, other):
|
|
self.sums -= other.sums
|
|
self.squares -= other.squares
|
|
self.samples -= other.samples
|
|
return self
|
|
|
|
def __iadd__(self, other):
|
|
self.sums += other.sums
|
|
self.squares += other.squares
|
|
self.samples += other.samples
|
|
return self
|
|
|
|
def __itruediv__(self, other):
|
|
if not other:
|
|
return self
|
|
self.sums /= other
|
|
self.squares /= other
|
|
self.samples /= other
|
|
return self
|
|
|
|
class FullDist(SimpleDist):
|
|
def __init__(self, sums, squares, samples, minval, maxval,
|
|
under, vec, over, min, max, bsize, size):
|
|
self.sums = sums
|
|
self.squares = squares
|
|
self.samples = samples
|
|
self.minval = minval
|
|
self.maxval = maxval
|
|
self.under = under
|
|
self.vec = vec
|
|
self.over = over
|
|
self.min = min
|
|
self.max = max
|
|
self.bsize = bsize
|
|
self.size = size
|
|
|
|
def display(self, name, desc, flags, precision):
|
|
import display
|
|
p = display.Print()
|
|
p.flags = flags
|
|
p.precision = precision
|
|
|
|
p.name = name + '.min_val'
|
|
p.value = self.minval
|
|
p.display()
|
|
|
|
p.name = name + '.max_val'
|
|
p.value = self.maxval
|
|
p.display()
|
|
|
|
p.name = name + '.underflow'
|
|
p.value = self.under
|
|
p.display()
|
|
|
|
i = self.min
|
|
for val in self.vec[:-1]:
|
|
p.name = name + '[%d:%d]' % (i, i + self.bsize - 1)
|
|
p.value = val
|
|
p.display()
|
|
i += self.bsize
|
|
|
|
p.name = name + '[%d:%d]' % (i, self.max)
|
|
p.value = self.vec[-1]
|
|
p.display()
|
|
|
|
|
|
p.name = name + '.overflow'
|
|
p.value = self.over
|
|
p.display()
|
|
|
|
SimpleDist.display(self, name, desc, flags, precision)
|
|
|
|
def comparable(self, other):
|
|
return self.min == other.min and self.max == other.max and \
|
|
self.bsize == other.bsize and self.size == other.size
|
|
|
|
def __eq__(self, other):
|
|
return self.sums == other.sums and self.squares == other.squares and \
|
|
self.samples == other.samples
|
|
|
|
def __isub__(self, other):
|
|
self.sums -= other.sums
|
|
self.squares -= other.squares
|
|
self.samples -= other.samples
|
|
|
|
if other.samples:
|
|
self.minval = min(self.minval, other.minval)
|
|
self.maxval = max(self.maxval, other.maxval)
|
|
self.under -= under
|
|
self.vec = map(lambda x,y: x - y, self.vec, other.vec)
|
|
self.over -= over
|
|
return self
|
|
|
|
def __iadd__(self, other):
|
|
if not self.samples and other.samples:
|
|
self = other
|
|
return self
|
|
|
|
self.sums += other.sums
|
|
self.squares += other.squares
|
|
self.samples += other.samples
|
|
|
|
if other.samples:
|
|
self.minval = min(self.minval, other.minval)
|
|
self.maxval = max(self.maxval, other.maxval)
|
|
self.under += other.under
|
|
self.vec = map(lambda x,y: x + y, self.vec, other.vec)
|
|
self.over += other.over
|
|
return self
|
|
|
|
def __itruediv__(self, other):
|
|
if not other:
|
|
return self
|
|
self.sums /= other
|
|
self.squares /= other
|
|
self.samples /= other
|
|
|
|
if self.samples:
|
|
self.under /= other
|
|
for i in xrange(len(self.vec)):
|
|
self.vec[i] /= other
|
|
self.over /= other
|
|
return self
|
|
|
|
class Dist(Statistic):
|
|
def display(self):
|
|
import display
|
|
if not display.all and not (self.flags & flags.printable):
|
|
return
|
|
|
|
self.dist.display(self.name, self.desc, self.flags, self.precision)
|
|
|
|
def comparable(self, other):
|
|
return self.name == other.name and \
|
|
self.dist.compareable(other.dist)
|
|
|
|
def __eq__(self, other):
|
|
return self.dist == other.dist
|
|
|
|
def __isub__(self, other):
|
|
self.dist -= other.dist
|
|
return self
|
|
|
|
def __iadd__(self, other):
|
|
self.dist += other.dist
|
|
return self
|
|
|
|
def __itruediv__(self, other):
|
|
if not other:
|
|
return self
|
|
self.dist /= other
|
|
return self
|
|
|
|
class VectorDist(Statistic):
|
|
def display(self):
|
|
import display
|
|
if not display.all and not (self.flags & flags.printable):
|
|
return
|
|
|
|
if isinstance(self.dist, SimpleDist):
|
|
return
|
|
|
|
for dist,sn,sd,i in map(None, self.dist, self.subnames, self.subdescs,
|
|
range(len(self.dist))):
|
|
if len(sn) > 0:
|
|
name = '%s.%s' % (self.name, sn)
|
|
else:
|
|
name = '%s[%d]' % (self.name, i)
|
|
|
|
if len(sd) > 0:
|
|
desc = sd
|
|
else:
|
|
desc = self.desc
|
|
|
|
dist.display(name, desc, self.flags, self.precision)
|
|
|
|
if (self.flags & flags.total) or 1:
|
|
if isinstance(self.dist[0], SimpleDist):
|
|
disttotal = SimpleDist( \
|
|
reduce(sums, [d.sums for d in self.dist]),
|
|
reduce(sums, [d.squares for d in self.dist]),
|
|
reduce(sums, [d.samples for d in self.dist]))
|
|
else:
|
|
disttotal = FullDist( \
|
|
reduce(sums, [d.sums for d in self.dist]),
|
|
reduce(sums, [d.squares for d in self.dist]),
|
|
reduce(sums, [d.samples for d in self.dist]),
|
|
min([d.minval for d in self.dist]),
|
|
max([d.maxval for d in self.dist]),
|
|
reduce(sums, [d.under for d in self.dist]),
|
|
reduce(sums, [d.vec for d in self.dist]),
|
|
reduce(sums, [d.over for d in self.dist]),
|
|
dist[0].min,
|
|
dist[0].max,
|
|
dist[0].bsize,
|
|
dist[0].size)
|
|
|
|
name = '%s.total' % (self.name)
|
|
desc = self.desc
|
|
disttotal.display(name, desc, self.flags, self.precision)
|
|
|
|
def comparable(self, other):
|
|
return self.name == other.name and \
|
|
alltrue(map(lambda x, y : x.comparable(y),
|
|
self.dist,
|
|
other.dist))
|
|
|
|
def __eq__(self, other):
|
|
return alltrue(map(lambda x, y : x == y, self.dist, other.dist))
|
|
|
|
def __isub__(self, other):
|
|
if isinstance(self.dist, (list, tuple)) and \
|
|
isinstance(other.dist, (list, tuple)):
|
|
for sd,od in zip(self.dist, other.dist):
|
|
sd -= od
|
|
else:
|
|
self.dist -= other.dist
|
|
return self
|
|
|
|
def __iadd__(self, other):
|
|
if isinstance(self.dist, (list, tuple)) and \
|
|
isinstance(other.dist, (list, tuple)):
|
|
for sd,od in zip(self.dist, other.dist):
|
|
sd += od
|
|
else:
|
|
self.dist += other.dist
|
|
return self
|
|
|
|
def __itruediv__(self, other):
|
|
if not other:
|
|
return self
|
|
if isinstance(self.dist, (list, tuple)):
|
|
for dist in self.dist:
|
|
dist /= other
|
|
else:
|
|
self.dist /= other
|
|
return self
|
|
|
|
class Vector2d(Statistic):
|
|
def display(self):
|
|
import display
|
|
if not display.all and not (self.flags & flags.printable):
|
|
return
|
|
|
|
d = display.VectorDisplay()
|
|
d.__dict__.update(self.__dict__)
|
|
|
|
if self.__dict__.has_key('ysubnames'):
|
|
ysubnames = list(self.ysubnames)
|
|
slack = self.x - len(ysubnames)
|
|
if slack > 0:
|
|
ysubnames.extend(['']*slack)
|
|
else:
|
|
ysubnames = range(self.x)
|
|
|
|
for x,sname in enumerate(ysubnames):
|
|
o = x * self.y
|
|
d.value = self.value[o:o+self.y]
|
|
d.name = '%s[%s]' % (self.name, sname)
|
|
d.display()
|
|
|
|
if self.flags & flags.total:
|
|
d.value = []
|
|
for y in range(self.y):
|
|
xtot = 0.0
|
|
for x in range(self.x):
|
|
xtot += self.value[y + x * self.x]
|
|
d.value.append(xtot)
|
|
|
|
d.name = self.name + '.total'
|
|
d.display()
|
|
|
|
def comparable(self, other):
|
|
return self.name == other.name and self.x == other.x and \
|
|
self.y == other.y
|
|
|
|
def __eq__(self, other):
|
|
return True
|
|
|
|
def __isub__(self, other):
|
|
return self
|
|
|
|
def __iadd__(self, other):
|
|
return self
|
|
|
|
def __itruediv__(self, other):
|
|
if not other:
|
|
return self
|
|
return self
|
|
|
|
def NewStat(source, data):
|
|
stat = None
|
|
if data.type == 'SCALAR':
|
|
stat = ScalarStat()
|
|
elif data.type == 'VECTOR':
|
|
stat = VectorStat()
|
|
elif data.type == 'DIST':
|
|
stat = Dist()
|
|
elif data.type == 'VECTORDIST':
|
|
stat = VectorDist()
|
|
elif data.type == 'VECTOR2D':
|
|
stat = Vector2d()
|
|
elif data.type == 'FORMULA':
|
|
stat = Formula()
|
|
|
|
stat.__dict__['source'] = source
|
|
stat.__dict__['bins'] = None
|
|
stat.__dict__['ticks'] = None
|
|
stat.__dict__.update(data.__dict__)
|
|
|
|
return stat
|
|
|