func (a *App) d3timelineInner(t *appdash.Trace, depth int) ([]timelineItem, error) { var items []timelineItem var events []appdash.Event if err := appdash.UnmarshalEvents(t.Span.Annotations, &events); err != nil { return nil, err } var u *url.URL if t.ID.Parent == 0 { var err error u, err = a.URLToTrace(t.ID.Trace) if err != nil { return nil, err } } else { var err error u, err = a.URLToTraceSpan(t.ID.Trace, t.ID.Span) if err != nil { return nil, err } } item := timelineItem{ Label: t.Span.Name(), FullLabel: t.Span.Name(), Data: t.Annotations.StringMap(), SpanID: t.Span.ID.Span.String(), URL: u.String(), } if !item.Valid() { return nil, ErrTimelineItemValidation } if t.Span.ID.Parent != 0 { item.ParentSpanID = t.Span.ID.Parent.String() } if depth <= 1 { item.Visible = true } for _, e := range events { if e, ok := e.(appdash.TimespanEvent); ok { start := e.Start().UnixNano() / int64(time.Millisecond) end := e.End().UnixNano() / int64(time.Millisecond) ts := timelineItemTimespan{ Start: start, End: end, } if t.Span.ID.Parent == 0 { ts.Label = e.Schema() item.Times = append(item.Times, &ts) } else { if item.Times == nil { item.Times = append(item.Times, &ts) } else { if item.Times[0].Start > start { item.Times[0].Start = start } if item.Times[0].End < end { item.Times[0].End = end } } } } } for _, ts := range item.Times { msec := time.Duration(item.Times[0].End-item.Times[0].Start) * time.Millisecond if msec > 0 { ts.Label = fmt.Sprintf("%s (%s)", item.Label, msec) ts.Duration = int64(msec) } } if len(item.Times) == 0 { // Items with a null times array will crash d3-timeline.js as it tries // to iterate over it. This means the trace doesn't have a single // TimespanEvent and is thus invalid. return nil, nil } items = append(items, item) for _, child := range t.Sub { subItems, err := a.d3timelineInner(child, depth+1) if err != nil { return nil, err } items = append(items, subItems...) } return items, nil }
// calcProfile calculates a profile for the given trace and appends it to the // given buffer (buf), which is then returned (prof). If an error is returned, // all other returned values are nil. The childProf is literally the *profile // associated with the given trace (t). func (a *App) calcProfile(buf []*profile, t *appdash.Trace) (prof []*profile, childProf *profile, err error) { // Unmarshal the trace's span events. var events []appdash.Event if err := appdash.UnmarshalEvents(t.Span.Annotations, &events); err != nil { return nil, nil, err } // Get the proper URL to the trace view. var u *url.URL if t.ID.Parent == 0 { u, err = a.URLToTrace(t.ID.Trace) if err != nil { return nil, nil, err } } else { u, err = a.URLToTraceSpan(t.ID.Trace, t.ID.Span) if err != nil { return nil, nil, err } } // Initialize the span's profile structure. We use either the span's given // name, or it's ID as a string if it has no given name. p := &profile{ Name: t.Span.Name(), URL: u.String(), } if len(p.Name) == 0 { p.Name = t.Span.ID.Span.String() } buf = append(buf, p) // Store the time for the largest timespan event the span has. for _, ev := range events { ts, ok := ev.(appdash.TimespanEvent) if !ok { continue } // To match the timeline properly we use floats and round up. msf := float64(ts.End().Sub(ts.Start())) / float64(time.Millisecond) ms := int64(msf + 0.5) if ms > p.Time { p.Time = ms } } // TimeChildren is our time + the children's time. p.TimeChildren = p.Time // The cumulative time is our time + all children's time. p.TimeCum = p.Time // Descend recursively into each sub-trace and calculate the profile for // each child span. for _, child := range t.Sub { buf, childProf, err = a.calcProfile(buf, child) if err != nil { return nil, nil, err } // Aggregate our direct children's time. p.TimeChildren += childProf.Time // As our child's profile has the cumulative time (which is initially, // it's self time) -- we can simply aggregate it here and we have our // trace's cumulative time (i.e. it is effectively recursive). p.TimeCum += childProf.TimeCum } return buf, p, nil }