blob: 7db04a0346e2679d0805e6d54544f3706202401d [file] [log] [blame]
// Contains DataFrame merge logic, similar to //perf/go/dataframe/dataframe.go
import {
DataFrame,
ParamSet,
Params,
ColumnHeader,
TraceSet,
ReadOnlyParamSet,
Trace,
} from '../json';
import {
addParamSet,
addParamsToParamSet,
fromKey,
toReadOnlyParamSet,
} from '../paramtools';
import { MISSING_DATA_SENTINEL } from '../const/const';
/** mergeColumnHeaders creates a merged header from the two given headers.
*
* I.e. {1,4,5} + {3,4} => {1,3,4,5}
*/
export function mergeColumnHeaders(
a: (ColumnHeader | null)[] | null,
b: (ColumnHeader | null)[] | null
): [
(ColumnHeader | null)[] | null,
{ [key: number]: number },
{ [key: number]: number },
] {
if (a === null || a.length === 0) {
return [b, simpleMap(0), simpleMap(b!.length)];
}
if (b === null || b.length === 0) {
return [a, simpleMap(a!.length), simpleMap(0)];
}
const aMap: { [key: number]: number } = {};
const bMap: { [key: number]: number } = {};
const numA = a.length;
const numB = b.length;
let pA = 0;
let pB = 0;
const ret: (ColumnHeader | null)[] = [];
for (; true; ) {
if (pA === numA && pB === numB) {
break;
}
if (pA === numA) {
// Copy in the rest of B.
for (let i = 0; i < numB; i++) {
bMap[i] = ret.length;
ret.push(b[i]);
}
break;
}
if (pB === numB) {
// Copy in the rest of A.
for (let i = pA; i < numA; i++) {
aMap[i] = ret.length;
ret.push(a[i]);
}
break;
}
if (a[pA]!.offset < b[pB]!.offset) {
aMap[pA] = ret.length;
ret.push(a[pA]);
pA += 1;
} else if (a[pA]!.offset > b[pB]!.offset) {
bMap[pB] = ret.length;
ret.push(b[pB]);
pB += 1;
} else {
aMap[pA] = ret.length;
bMap[pB] = ret.length;
ret.push(a[pA]);
pA += 1;
pB += 1;
}
}
return [ret, aMap, bMap];
}
/** join creates a new DataFrame that is the union of 'a' and 'b'.
*
* Will handle the case of a and b having data for different sets of commits,
* i.e. a.Header doesn't have to equal b.Header.
*/
export function join(a: DataFrame, b: DataFrame): DataFrame {
if (a === null) {
return b;
}
const [header, aMap, bMap] = mergeColumnHeaders(a.header, b.header);
const ret: DataFrame = {
header: header,
traceset: {} as TraceSet,
} as DataFrame;
if (a.header!.length === 0) {
a.header = b.header;
}
ret.skip = b.skip;
const ps = ParamSet({});
addParamSet(ps, a.paramset);
addParamSet(ps, b.paramset);
normalize(ps);
ret.paramset = toReadOnlyParamSet(ps);
const traceLen = ret.header!.length;
for (const [key, sourceTrace] of Object.entries(a.traceset)) {
if (!ret.traceset[key]) {
ret.traceset[key] = Trace(
new Array<number>(traceLen).fill(MISSING_DATA_SENTINEL)
);
}
const destTrace = ret.traceset[key];
(sourceTrace as number[]).forEach((sourceValue, sourceOffset) => {
destTrace[aMap[sourceOffset]] = sourceValue;
});
}
for (const [key, sourceTrace] of Object.entries(b.traceset)) {
if (!ret.traceset[key]) {
ret.traceset[key] = Trace(
new Array<number>(traceLen).fill(MISSING_DATA_SENTINEL)
);
}
const destTrace = ret.traceset[key];
(sourceTrace as number[]).forEach((sourceValue, sourceOffset) => {
destTrace[bMap[sourceOffset]] = sourceValue;
});
}
return ret;
}
/** buildParamSet rebuilds d.paramset from the keys of d.traceset. */
export function buildParamSet(d: DataFrame): void {
const paramSet = ParamSet({});
for (const key of Object.keys(d.traceset)) {
const params = fromKey(key);
addParamsToParamSet(paramSet, params);
}
normalize(paramSet);
d.paramset = toReadOnlyParamSet(paramSet);
}
/** timestampBounds returns the timestamps for the first and last header values.
* If df is null, or its header value is null or of length 0, this will return
* [NaN, NaN].
*/
export function timestampBounds(df: DataFrame | null): [number, number] {
if (df === null || df.header === null || df.header.length === 0) {
return [NaN, NaN];
}
const ret: [number, number] = [NaN, NaN];
ret[0] = df.header![0]!.timestamp;
ret[1] = df.header![df.header!.length - 1]!.timestamp;
return ret;
}
function normalize(ps: ParamSet): void {
for (const [k, v] of Object.entries(ps)) {
(v as string[]).sort();
}
}
function simpleMap(n: number): { [key: number]: number } {
const ret: { [key: number]: number } = {};
for (let i = 0; i < n; i += 1) {
ret[i] = i;
}
return ret;
}