Chronaxis用户手册English
本页目录

chronaxis.execution.result_aggregations

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""在最终结果行产生时按具名引用分组。只保存归约状态和精确分位数所需的样本。"""23from fractions import Fraction4from math import isfinite5from ..core.quantiles import ExactQuantileBuffer6from ..core.types import SignalDType as D, QualityFlag as Q7from ..planning.aggregations import aggregation_unit8from ..results.aggregation_row import SummaryAggregationRow9from ..results.summary import SummaryCalculationRow, SummaryEventRow, SummarySegmentRow, SummarySequenceRow10from ..results.budget import _reserve_owned_ram, _release_owned_ram, _cleanup_owner111213def discovery_rules(plan, spec):14    """定位总体发现所依赖的原规则。参数: 主计划与聚合。返回: 规则ID元组。异常: 引用未校验。"""15    if spec.source_kind=='rule':16        return (spec.source_id,)17    if spec.source_kind=='sequence':18        pair=next(item for item in plan.sequences if item.sequence_id==spec.source_id)19        return tuple(dict.fromkeys((pair.start_rule,pair.response_rule)))20    if spec.source_kind=='calculation':21        calc=next(item for item in plan.calculations if item.calculation_id==spec.source_id)22        if calc.scope=='session':23            return ()24        scope,parent=calc.scope.split(':')25        if scope=='event':26            return (parent,)27    else:28        parent=spec.source_id29    window=next(w for w in plan.windows if w.window_id==parent)30    return (window.event,)313233class _Group:34    """一个实际键的有限状态。二阶和在精确有理数域累计,输出边界检查数值范围。"""3536    def __init__(self):37        """创建空候选和零计数。参数: 无。返回: 新组。异常: 分配错误传播。"""38        self.total=self.used=self.excluded=self.bad=self.partials=039        self.matched=self.timeout=self.insufficient=040        self.sum=self.squares=Fraction(0)41        self.quality=Q.NONE42        self.quantile=None43        self.integer=True4445    def accept(self,spec,row,budget,backend,dtype,needs_coverage):46        """从一条源行提取候选、分类及实际数值。参数: 计划、行、预算、后端、原dtype。返回: None。异常: 预算或模型错误。"""47        self.total+=148        if spec.source_kind=='sequence':49            self.matched+=row.status=='matched'50            self.timeout+=row.status=='no_response'51            self.insufficient+=row.status=='indeterminate'52        if spec.operation=='count':53            # 计已观测对象身份。已确认请求的响应未知不使该请求本身消失。54            self.used+=155            return56        if spec.source_kind=='sequence':57            if row.status=='no_response':58                self.excluded+=159                return60            if row.status=='indeterminate':61                self.bad+=162                self.quality|=row.quality63                return64            if row.quality & Q.ESTIMATED:65                self.bad+=166                self.quality|=row.quality67                return68            value=row.delay_ns69        else:70            if row.unit!=spec.input_unit:71                raise ValueError('指标结果行单位与同一来源计划不符')72            if row.status=='complete' and row.value is None and row.reason=='empty_selection':73                self.excluded+=174                return75            if (row.value is None or row.status=='indeterminate' or getattr(row,'coverage_status',None)=='indeterminate'76                    or needs_coverage and not hasattr(row,'coverage_status')77                    or row.status=='partial' and not spec.include_partial):78                self.bad+=179                self.quality|=row.quality or Q.MISSING80                return81            self.partials+=row.status=='partial'82            value=row.value83        self.quality|=row.quality84        self.used+=185        self.integer &= type(value) is int86        if spec.operation in ('quantile','median'):87            if self.quantile is None:88                self.quantile=ExactQuantileBuffer('sample_'+spec.operation,spec.q,dtype,backend,budget)89            if dtype is D.FLOAT64 and type(value) is int and int(float(value))!=value:90                self.quality|=Q.PRECISION_LOSS91                self.bad+=192                self.used-=193                self.partials-=getattr(row,'status','')=='partial'94                return95            if backend=='numpy':96                import numpy as np97                from ..core.numeric_numpy import DTYPES98                self.quantile.append(np.asarray([value],dtype=DTYPES[dtype]),np.ones(1,dtype=bool))99            else:100                self.quantile.append((value,))101        else:102            exact=Fraction(value)103            self.sum+=exact104            if spec.operation=='variance':105                self.squares+=exact*exact106107    def value(self,spec,factor,shift):108        """最终一次换算,不平均行覆盖率或块分位数。参数: 计划及已核单位系数。返回: 数值、原因、质量。异常: 原预算/排序错误。"""109        if spec.operation=='count':110            return self.used,'ok',Q.NONE111        if not self.used:112            return None,'empty_population',Q.NONE113        if spec.operation=='variance' and self.used<=spec.ddof:114            return None,'insufficient_count',Q.NONE115        if spec.operation in ('median','quantile'):116            value,quality=self.quantile.finish()117            if value is None:118                return None,'numeric_error',quality119            number=Fraction(value)*factor+shift120            integral=type(value) is int121        elif spec.operation=='variance':122            number=(self.squares-self.sum*self.sum/self.used)/(self.used-spec.ddof)*factor123            integral=False124        elif spec.operation=='mean':125            number=self.sum/self.used*factor+shift126            integral=False127        else:128            number=self.sum*factor+shift*self.used129            integral=self.integer130        if integral and isinstance(number,Fraction) and number.denominator==1:131            if -(1<<63)<=number.numerator<1<<64:132                return number.numerator,'ok',Q.NONE133            return None,'numeric_error',Q.OVERFLOW134        try:135            result=float(number)136        except OverflowError:137            return None,'numeric_error',Q.OVERFLOW138        if (not isfinite(result) or result==0 and number139                or self.integer and abs(number)>=1<<53 and Fraction(result)!=number):140            return None,'numeric_error',Q.PRECISION_LOSS141        return result,'ok',Q.NONE142143144class ResultAggregations:145    """订阅同一次运行的必要源行,分组先预约,异常由拥有者统一关闭。"""146147    def __init__(self,plan,budget):148        """预留有限声明、已知空组和结果元数据。参数: 已校验计划和共享账本。返回: owner。异常: 配置/预算错误含清理句柄。"""149        self._plan,self._budget=plan,budget150        self._held_cost=self._group_cost=0151        self._groups={}152        self._subscribers={}153        self._types={}154        self._scales={}155        self._coverage=set()156        self._seen=set()157        self._closed=False158        try:159            _reserve_owned_ram(self,8192+8192*len(plan.aggregations),'_group_cost')160            for spec in plan.aggregations:161                self._subscribers.setdefault((spec.source_kind,spec.source_id),[]).append(spec)162                self._scales[spec]=aggregation_unit(spec.operation,spec.input_unit,spec.unit)[1:]163                self._types[spec]=self._input_dtype(spec)164                if spec.source_kind=='calculation' and any(c.calculation_id==spec.source_id and165                        c.minimum_coverage>0 for c in plan.calculations):166                    self._coverage.add(spec)167        except BaseException as exc:168            _cleanup_owner(self,exc,'aggregation_cleanup_owner','结果聚合')169            raise170171    def _input_dtype(self,spec):172        """确定分位数排序列的原类型。参数: 已校验聚合。返回: dtype。异常: 引用不符。"""173        if spec.source_kind=='sequence':174            return D.INT64175        if spec.source_kind!='calculation' or spec.operation not in ('median','quantile'):176            return D.FLOAT64177        calc=next(c for c in self._plan.calculations if c.calculation_id==spec.source_id)178        if self._plan.extensions is not None and calc.operation in self._plan.extensions.reductions:179            from ..core.calculations import CalculationDefinition,calculation_conversion180            from ..core.models import Interval181            value=self._plan.extensions.reductions[calc.operation].output182            unit=CalculationDefinition('stat','sample_minimum',Interval(0,0),output_unit=calc.unit,config_version=4)183            return calculation_conversion(unit,value.unit).dtype(value.dtype)184        if calc.operation.endswith('_count'):185            return D.UINT64186        if calc.operation in ('sample_minimum','sample_maximum','min_time','max_time'):187            from ..core.calculations import CalculationDefinition,calculation_conversion188            from ..core.models import Interval189            unit=CalculationDefinition('stat',calc.operation,Interval(0,0),output_unit=calc.unit,config_version=4)190            return calculation_conversion(unit,calc.signal.root.unit).dtype(calc.signal.root.dtype)191        return D.FLOAT64192193    def _group(self,spec,row):194        """按稳定字段取得或创建组。参数: 计划、实际源行或已知固定空组。返回: 状态。异常: 预算或键不合法。"""195        dataset=self._plan.dataset196        values={'dataset_id':row.dataset_id if row is not None else dataset.dataset_id,197                'session_id':row.session_id if row is not None else dataset.session_id,198                'entity_id':row.entity_id if row is not None else dataset.entity,199                spec.source_kind+'_id':spec.source_id}200        if 'parent_id' in spec.group_by:201            values['parent_id']=row.parent_id202        key=tuple(values[k] for k in spec.group_by)203        identity=(spec,key)204        if identity not in self._groups:205            _reserve_owned_ram(self,32768+16*sum(len(k) for k in key),'_group_cost')206            self._groups[identity]=_Group()207        return self._groups[identity]208209    def accept(self,row):210        """只分派实际受订阅的结果行。参数: 本次产出的行。返回: None。异常: 预算/输入矛盾。"""211        if isinstance(row,SummaryCalculationRow):212            key=('calculation',row.calculation_id)213        elif isinstance(row,SummarySequenceRow):214            key=('sequence',row.rule_id)215        elif isinstance(row,SummaryEventRow):216            key=('rule',row.rule_id)217        elif isinstance(row,SummarySegmentRow):218            key=('window',row.window_id)219        else:220            return221        for spec in self._subscribers.get(key,()):222            self._seen.add(spec)223            self._group(spec,row).accept(spec,row,self._budget,self._plan.execution.backend,self._types[spec],spec in self._coverage)224225    def rows(self,rules,sequence):226        """在原规则完成后交付总体判定及组结果。参数: 原规则owner、首序号。返回: 逐组行。异常: 未完成或预算错误。"""227        by_id={owner._request.rule_id:owner for owner in rules}228        ds=self._plan.dataset229        for spec in self._plan.aggregations:230            if spec not in self._seen and 'parent_id' not in spec.group_by:231                self._group(spec,None)232        for (spec,key),group in self._groups.items():233            evidence=[by_id[name].count_evidence(ds.domain) for name in discovery_rules(self._plan,spec)]234            complete=all(e[2] for e in evidence)235            observed=all(e[0] for e in evidence)236            determinate=all(e[1] for e in evidence)237            quality=group.quality238            for _,_,_,flags in evidence:239                quality|=flags240            partial=not complete or group.bad>0 or group.partials>0241            number,reason,flag=group.value(spec,*self._scales[spec])242            quality|=flag243            if not group.used and (not observed or not determinate or group.bad):244                number,status,reason=None,'indeterminate','no_observation' if not observed else 'insufficient_data'245            elif reason=='numeric_error' or reason=='insufficient_count':246                status='indeterminate'247            elif partial:248                status,reason='partial','partial_population'249                if spec.partial_policy=='strict':250                    number=None251            else:252                status='empty' if number is None else 'complete'253            identity=dict(zip(spec.group_by,key))254            yield SummaryAggregationRow(sequence,identity.get('dataset_id',ds.dataset_id),identity.get('entity_id',ds.entity),255                identity.get('session_id',ds.session_id),spec.name,spec.source_kind,256                spec.source_id,spec.operation,spec.group_by,key,number,spec.unit,status,reason,group.total,257                group.used,group.excluded,group.bad,group.partials,group.matched,group.timeout,group.insufficient,258                0,complete,quality)259            sequence+=1260261    def close(self):262        """释放精确分位数和自己的分组费用,可重试。参数: 无。返回: None。异常: 原账本错误传播。"""263        if self._closed:264            return265        for group in self._groups.values():266            if group.quantile is not None:267                group.quantile.close()268        self._groups.clear()269        _release_owned_ram(self,self._group_cost,'_group_cost')270        self._closed=True271
Chronaxis 用户手册 · 离线阅读