// SumFunc implements Func and sums the values of all argument // traces into a single trace. // // MISSING_DATA_SENTINEL values are not included in the sum. Note that if all // the values at an index are MISSING_DATA_SENTINEL then the sum will be // MISSING_DATA_SENTINEL. func (SumFunc) Eval(ctx *Context, node *Node) ([]*types.PerfTrace, error) { if len(node.Args) != 1 { return nil, fmt.Errorf("Sum() takes a single argument.") } if node.Args[0].Typ != NodeFunc { return nil, fmt.Errorf("Sum() takes a function argument.") } traces, err := node.Args[0].Eval(ctx) if err != nil { return nil, fmt.Errorf("Sum() argument failed to evaluate: %s", err) } if len(traces) == 0 { return traces, nil } ret := types.NewPerfTraceN(len(traces[0].Values)) ret.Params()["id"] = tiling.AsFormulaID(ctx.formula) for i, _ := range ret.Values { sum := 0.0 count := 0 for _, tr := range traces { if v := tr.Values[i]; v != config.MISSING_DATA_SENTINEL { sum += v count += 1 } } if count > 0 { ret.Values[i] = sum } } return []*types.PerfTrace{ret}, nil }
// addCalculatedTraces adds the traces returned from evaluating the given // formula over the given tile to the QueryResponse. func addCalculatedTraces(qr *QueryResponse, tile *tiling.Tile, formula string) error { ctx := parser.NewContext(tile) traces, err := ctx.Eval(formula) if err != nil { return fmt.Errorf("Failed to evaluate formula %q: %s", formula, err) } hasFormula := false for _, tr := range traces { if tiling.IsFormulaID(tr.Params()["id"]) { hasFormula = true } tg := traceGuiFromTrace(tr, tr.Params()["id"], tile) qr.Traces = append(qr.Traces, tg) } if !hasFormula { // If we haven't added the formula trace to the response yet, add it in now. f := types.NewPerfTraceN(len(tile.Commits)) tg := traceGuiFromTrace(f, tiling.AsFormulaID(formula), tile) qr.Traces = append(qr.Traces, tg) } return nil }
// geoFunc implements Func and merges the values of all argument // traces into a single trace with a geometric mean. // // MISSING_DATA_SENTINEL and negative values are not included in the mean. // Note that if all the values at an index are MISSING_DATA_SENTINEL or // negative then the mean will be MISSING_DATA_SENTINEL. func (GeoFunc) Eval(ctx *Context, node *Node) ([]*types.PerfTrace, error) { if len(node.Args) != 1 { return nil, fmt.Errorf("geo() takes a single argument.") } if node.Args[0].Typ != NodeFunc { return nil, fmt.Errorf("geo() takes a function argument.") } traces, err := node.Args[0].Eval(ctx) if err != nil { return nil, fmt.Errorf("geo() argument failed to evaluate: %s", err) } if len(traces) == 0 { return traces, nil } ret := types.NewPerfTraceN(len(traces[0].Values)) ret.Params()["id"] = tiling.AsFormulaID(ctx.formula) for i, _ := range ret.Values { // We're accumulating a product, but in log-space to avoid large N overflow. sumLog := 0.0 count := 0 for _, tr := range traces { if v := tr.Values[i]; v >= 0 && v != config.MISSING_DATA_SENTINEL { sumLog += math.Log(v) count += 1 } } if count > 0 { // The geometric mean is the N-th root of the product of N terms. // In log-space, the root becomes a division, then we translate back to normal space. ret.Values[i] = math.Exp(sumLog / float64(count)) } } return []*types.PerfTrace{ret}, nil }