@kui-shell/plugin-tekton
Version:
Visualizations for Tekton Pipelines
257 lines • 12.7 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import Debug from 'debug';
import * as prettyPrintDuration from 'pretty-ms';
import { empty, prettyPrintTime, i18n } from '@kui-shell/core';
import success from '../../lib/success';
import { getPipelineFromRef, getTasks } from '../fetch';
const strings = i18n('plugin-tekton', 'modes');
const debug = Debug('plugins/tekton/models/modes/trace');
export const render = (tab, activations, container, opts = {}) => {
const { noCrop = false, showStart = false, showTimeline = true } = opts;
debug('trace', activations);
const legendHTMLtext = `<div class='legend-stripe'><div class='legend-entry' data-legend-type='queueing-delays' data-balloon='The time this activation waited for free execution resources' data-balloon-pos='left'>Queueing Delays<div class='legend-icon is-waitTime'></div></div><div class='legend-entry' data-legend-type='container-initialization' data-balloon='The "cold start time", i.e. time spent initializing a container' data-balloon-pos='left'>Container Initialization<div class='legend-icon is-initTime'></div></div><div class='legend-entry' data-legend-type='execution-time' data-balloon='The time this activation spent executing your code' data-balloon-pos='left'>Execution Time<div class='legend-icon is-runTime'></div></div><div class='legend-entry' data-legend-type='failures' data-balloon='The activation failed to complete' data-balloon-pos='left'>Failures<div class='legend-icon is-success-false'></div></div></div>`;
const legend = document.createElement('div');
container.appendChild(legend);
legend.className = 'legend-trace legend-list';
legend.innerHTML = legendHTMLtext;
const logTable = document.createElement('table');
logTable.className = 'log-lines log-lines-loose';
container.appendChild(logTable);
const first = 0;
const start = activations[first].start;
const maxEnd = activations.reduce((max, activation) => Math.max(max, activation.end || activation.start + 1), 0);
const dur = Math.max(1, maxEnd - start, maxEnd - start);
const tgap = 0;
const gaps = new Array(activations.length).fill(0);
const normalize = (value, idx) => {
return (value - start - gaps[idx]) / (dur - tgap);
};
activations.forEach((activation, idx) => {
const isSuccess = !activation.end
? true
: activation.statusCode !== undefined
? activation.statusCode === 0
: activation.response && activation.response.success;
const line = logTable.insertRow(-1);
line.className = 'log-line entity';
line.classList.add('activation');
line.setAttribute('data-name', activation.name);
if (idx === 0)
line.classList.add('log-line-root');
const nextCell = () => line.insertCell(-1);
const id = nextCell();
const clicky = document.createElement('span');
clicky.className = 'clickable';
id.appendChild(clicky);
id.className = 'log-field';
if (noCrop)
id.classList.add('full-width');
clicky.innerText = activation.activationId;
id.setAttribute('data-activation-id', id.innerText);
const name = nextCell();
const nameClick = document.createElement('span');
name.className = 'slightly-deemphasize log-field entity-name';
nameClick.className = 'clickable';
nameClick.innerText = activation.name;
name.appendChild(nameClick);
const duration = nextCell();
duration.className = 'somewhat-smaller-text log-field log-field-right-align duration-field';
duration.classList.add(isSuccess ? 'green-text' : 'red-text');
if (activation.end) {
duration.innerText = prettyPrintDuration(activation.end - activation.start);
}
else {
duration.innerText = prettyPrintDuration(1);
}
const waitTime = 0;
const initTime = 0;
if (showTimeline) {
const timeline = nextCell();
empty(timeline);
const isRootBar = idx === 0;
timeline.className = 'log-field log-line-bar-field';
const bar = document.createElement('div');
bar.style.position = 'absolute';
bar.classList.add('log-line-bar');
bar.classList.add(`is-success-${isSuccess}`);
const left = normalize(activation.start + initTime, idx);
const right = normalize(idx === 0 ? maxEnd : activation.end || activation.start + initTime + 1, idx);
const width = right - left;
const balloonPos = right > 0.9 ? 'left' : 'right';
bar.style.left = 100 * left + '%';
bar.style.width = 100 * width + '%';
bar.setAttribute('data-balloon', prettyPrintDuration(activation.end ? activation.end - activation.start - initTime : initTime));
bar.setAttribute('data-balloon-pos', balloonPos);
bar.onmouseover = () => legend.setAttribute('data-hover-type', isSuccess ? 'execution-time' : 'failures');
bar.onmouseout = () => legend.removeAttribute('data-hover-type');
let initTimeBar;
let waitTimeBar;
if (initTime > 0 && !isRootBar) {
initTimeBar = document.createElement('div');
const l = normalize(activation.start, idx);
const w = normalize(activation.start + initTime, idx) - l;
initTimeBar.style.left = 100 * l + '%';
initTimeBar.style.width = 100 * w + '%';
initTimeBar.style.position = 'absolute';
initTimeBar.classList.add('log-line-bar');
initTimeBar.classList.add('is-initTime');
initTimeBar.onmouseover = () => legend.setAttribute('data-hover-type', 'container-initialization');
initTimeBar.onmouseout = () => legend.removeAttribute('data-hover-type');
if (initTime === activation.duration) {
initTimeBar.classList.add(`is-success-false`);
}
else {
initTimeBar.classList.add(`is-success-true`);
}
initTimeBar.setAttribute('data-balloon', prettyPrintDuration(initTime));
initTimeBar.setAttribute('data-balloon-pos', balloonPos);
}
if (waitTime > 0 && !isRootBar) {
waitTimeBar = document.createElement('div');
const l = normalize(activation.start - waitTime, idx);
const w = normalize(activation.start, idx) - l;
waitTimeBar.style.left = 100 * l + '%';
waitTimeBar.style.width = 100 * w + '%';
waitTimeBar.style.position = 'absolute';
waitTimeBar.classList.add('log-line-bar');
waitTimeBar.classList.add('is-waitTime');
waitTimeBar.setAttribute('data-balloon', prettyPrintDuration(waitTime));
waitTimeBar.setAttribute('data-balloon-pos', balloonPos);
waitTimeBar.onmouseover = () => legend.setAttribute('data-hover-type', 'queueing-delays');
waitTimeBar.onmouseout = () => legend.removeAttribute('data-hover-type');
}
if (balloonPos === 'right') {
timeline.appendChild(bar);
if (initTimeBar)
timeline.appendChild(initTimeBar);
if (waitTimeBar)
timeline.appendChild(waitTimeBar);
}
else {
if (waitTimeBar)
timeline.appendChild(waitTimeBar);
if (initTimeBar)
timeline.appendChild(initTimeBar);
timeline.appendChild(bar);
}
}
if (showStart) {
const start = nextCell();
const startInner = document.createElement('span');
const previous = activations[idx - 1];
const previousWaitTime = 0;
const previousStart = previous && previous.start - previousWaitTime;
const time = prettyPrintTime(activation.start - waitTime, 'short', previousStart);
start.className =
'somewhat-smaller-text lighter-text log-field log-field-right-align start-time-field timestamp-like';
start.appendChild(startInner);
if (typeof time === 'string') {
startInner.innerText = time;
}
else {
empty(startInner);
startInner.appendChild(time);
}
}
});
};
function makeRunActivationLike(run) {
const start = run && run.status && run.status.startTime && new Date(run.status.startTime);
const end = run && run.status && run.status.completionTime && new Date(run.status.completionTime);
const duration = start && end && end.getTime() - start.getTime();
return {
activationId: run.metadata.name,
name: run.spec.pipelineRef.name,
start: start && start.getTime(),
end: end && end.getTime(),
duration,
response: {
success: success(run.status.conditions)
}
};
}
function makeSymbolTables(pipeline, jsons) {
const taskName2Task = jsons
.filter(_ => _.kind === 'Task')
.reduce((symtab, task) => {
symtab[task.metadata.name] = task;
return symtab;
}, {});
const taskRefName2Task = pipeline.spec.tasks.reduce((symtab, taskRef) => {
symtab[taskRef.name] = taskName2Task[taskRef.taskRef.name];
return symtab;
}, {});
return { taskRefName2Task };
}
function makeTaskRunsActivationLike(run, pipeline, jsons) {
const runs = run && run.status.taskRuns;
const { taskRefName2Task } = makeSymbolTables(pipeline, jsons);
const activations = Object.keys(runs || []).reduce((M, _) => {
const taskRun = runs[_];
const taskRefName = taskRun.pipelineTaskName;
const task = taskRefName2Task[taskRefName];
if (!task) {
console.error('!! task not found', taskRefName, taskRefName2Task);
}
else {
taskRun.status.steps.forEach(stepRun => {
const start = new Date(stepRun.terminated.startedAt).getTime();
const end = new Date(stepRun.terminated.finishedAt).getTime();
const success = stepRun.terminated.reason !== 'Error';
M.push({
activationId: taskRun.pipelineTaskName,
name: stepRun.name,
start,
end,
duration: end - start,
response: {
success
}
});
});
}
return M;
}, []);
activations.sort((a, b) => a.start - b.start);
return activations;
}
export const traceView = (tab, run, pipeline, jsons) => {
const content = document.createElement('div');
content.classList.add('padding-content', 'repl-result');
content.style.flex = '1';
content.style.display = 'flex';
content.style.flexDirection = 'column';
content.style.overflowX = 'hidden';
const runActivation = makeRunActivationLike(run);
render(tab, [runActivation].concat(makeTaskRunsActivationLike(run, pipeline, jsons)), content);
const badges = ['Tekton'];
return {
type: 'custom',
isEntity: true,
name: run.metadata.name,
packageName: run.metadata.namespace,
prettyType: 'PipelineRun',
duration: runActivation.duration,
badges,
content
};
};
const traceMode = {
mode: 'trace',
label: strings('trace'),
content: (tab, resource) => __awaiter(void 0, void 0, void 0, function* () {
const [pipeline, tasks] = yield Promise.all([getPipelineFromRef(tab, resource), getTasks(tab)]);
return traceView(tab, resource, pipeline, tasks);
}),
defaultMode: true
};
export default traceMode;
//# sourceMappingURL=trace.js.map