-
Notifications
You must be signed in to change notification settings - Fork 575
Rewrite computeSelfTime to improve performance on Trace Statistics page #2767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7eaec1e
7b7286d
5935bd1
03532d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,6 @@ | |
|
||
import memoizeOne from 'memoize-one'; | ||
import _uniq from 'lodash/uniq'; | ||
import DRange from 'drange'; | ||
import { Trace, Span } from '../../../types/trace'; | ||
import { ITableSpan } from './types'; | ||
import colorGenerator from '../../../utils/color-generator'; | ||
|
@@ -43,23 +42,53 @@ function getChildOfSpans(parentID: string, allSpans: Span[]): Span[] { | |
return memoizedParentChildOfMap(allSpans)[parentID] || []; | ||
} | ||
|
||
function computeSelfTime(span: Span, allSpans: Span[]): number { | ||
if (!span.hasChildren) return span.duration; | ||
// We want to represent spans as half-open intervals like [startTime, startTime + duration). | ||
// This way the subtraction preserves the right boundaries. However, DRange treats all | ||
// intervals as inclusive. For example, | ||
// range(1, 10).subtract(4, 8) => range([1, 3], [9-10]) | ||
// length=(3-1)+(10-9)=2+1=3 | ||
// In other words, we took an interval of length=10-1=9 and subtracted length=8-4=4. | ||
// We should've ended up with length 9-4=5, but we got 3. | ||
// To work around that, we multiply start/end times by 10 and subtract one from the end. | ||
// So instead of [1-10] we get [10-99]. This makes the intervals work like half-open. | ||
const spanRange = new DRange(10 * span.startTime, 10 * (span.startTime + span.duration) - 1); | ||
const children = getChildOfSpans(span.spanID, allSpans); | ||
children.forEach(child => { | ||
spanRange.subtract(10 * child.startTime, 10 * (child.startTime + child.duration) - 1); | ||
}); | ||
return Math.round(spanRange.length / 10); | ||
function computeSelfTime(parentSpan: Span, allSpans: Span[]): number { | ||
if (!parentSpan.hasChildren) return parentSpan.duration; | ||
|
||
let parentSpanSelfTime = parentSpan.duration; | ||
let previousChildEndTime = parentSpan.startTime; | ||
|
||
const children = getChildOfSpans(parentSpan.spanID, allSpans).sort((a, b) => a.startTime - b.startTime); | ||
|
||
const parentSpanEndTime = parentSpan.startTime + parentSpan.duration; | ||
|
||
for (let index = 0; index < children.length; index++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the alternative algorithm I had in mind:
I asked Gemini to implement it, it got this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO this is exactly the same algorithm, just implemented a bit differently. Initially wanted to keep the code dumb, so I wanted to avoid using Math.max/min. But using Math.max/Math.min should be fine, because comments with examples are very helpful. Now I don't have any preference for which version is easier to understand. Please let me know which version you prefer. I made the changes in 03532d7 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect the biggest cost to come from sorting the children repeatedly, making the whole thing an O(N^3) complexity, while I think it's possible to do it with O(N^2) if we sort first, the construct the tree from that sorted order. As for the algorithm, min/max makes it a lot easier (for me at lest) to reason as to what's happening, since it makes the logic linear, vs. reasoning about which edge case in the diagrams represent and whether all edge cases are actually covered. In my algo I only need to think about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const child = children[index]; | ||
|
||
const childEndTime = child.startTime + child.duration; | ||
const childStartsAfterParentEnded = child.startTime > parentSpanEndTime; | ||
const childEndsBeforePreviousChild = childEndTime < previousChildEndTime; | ||
|
||
// parent |..................| | ||
// child |.......| - previousChild | ||
// child |.....| - childEndsBeforePreviousChild is true, skipped | ||
// child |......| - childStartsAfterParentEnded is true, skipped | ||
if (childStartsAfterParentEnded || childEndsBeforePreviousChild) { | ||
continue; | ||
} | ||
|
||
// parent |.....................| | ||
// child |.......| - previousChild | ||
// child |.....| - nonOverlappingStartTime is previousChildEndTime | ||
// child |.....| - nonOverlappingStartTime is child.startTime | ||
const nonOverlappingStartTime = Math.max(previousChildEndTime, child.startTime); | ||
const childEndTimeOrParentEndTime = Math.min(parentSpanEndTime, childEndTime); | ||
|
||
const nonOverlappingDuration = childEndTimeOrParentEndTime - nonOverlappingStartTime; | ||
parentSpanSelfTime -= nonOverlappingDuration; | ||
|
||
// last span which can be included in self time calculation, because it ends after parent span ends | ||
// parent |......................| | ||
// child |.....| - last span included in self time calculation | ||
// child |.........| - skipped | ||
if (childEndTimeOrParentEndTime === parentSpanEndTime) { | ||
break; | ||
} | ||
|
||
previousChildEndTime = childEndTime; | ||
} | ||
|
||
return parentSpanSelfTime; | ||
} | ||
|
||
function computeColumnValues(trace: Trace, span: Span, allSpans: Span[], resultValue: StatsPerTag) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be more efficient if the full
allSpans
array was sorted once, and then direct children arrays picked from it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if this actually will be faster. But I am almost sure that it is not worth spending any time on optimizing this.