From 2c88b543443fe798c3287c6fbd02f96c0e00d90b Mon Sep 17 00:00:00 2001 From: YuanhuanO Date: Wed, 26 Jun 2024 20:42:20 -0400 Subject: [PATCH] widget and component view graph overlay --- daisen/static/src/componentview.ts | 277 ++++++++++++++++++++++++++++- daisen/static/src/taskpage.ts | 29 ++- daisen/static/src/widget.ts | 54 +++++- 3 files changed, 340 insertions(+), 20 deletions(-) diff --git a/daisen/static/src/componentview.ts b/daisen/static/src/componentview.ts index 6f9200d..1fabf05 100644 --- a/daisen/static/src/componentview.ts +++ b/daisen/static/src/componentview.ts @@ -4,6 +4,12 @@ import TaskRenderer from "./taskrenderer"; import XAxisDrawer from "./xaxisdrawer"; import { ScaleLinear } from "d3"; import { Task, Dim } from "./task"; +import { Widget, TimeValue } from "./widget"; + +type DataObject = { + info_type: string; + data: TimeValue[]; +}; class ComponentView { _yIndexAssigner: TaskYIndexAssigner; @@ -20,22 +26,73 @@ class ComponentView { _endTime: number; _xScale: ScaleLinear; _tasks: Array; + _widget: Widget; + _graphContentWidth: number; + _graphContentHeight: number; + _graphWidth: number; + _graphHeight: number; + _titleHeight: number; + _graphPaddingTop: number; + _widgetHeight: number; + _widgetWidth: number; + _yAxisWidth: number; + _xAxisHeight: number; + _primaryAxis: string; + _secondaryAxis: string; + _numDots:number; + _componentName: string; + _svg: SVGElement; + _yScale: d3.ScaleLinear; + _primaryYScale: d3.ScaleLinear; + _secondaryYScale: d3.ScaleLinear; + _primaryAxisData: object; + _secondaryAxisData: object; constructor( yIndexAssigner: TaskYIndexAssigner, taskRenderer: TaskRenderer, - xAxisDrawer: XAxisDrawer + xAxisDrawer: XAxisDrawer, + widget: Widget ) { this._yIndexAssigner = yIndexAssigner; this._taskRenderer = taskRenderer; this._xAxisDrawer = xAxisDrawer; + this._widget = widget; this._marginTop = 5; this._marginLeft = 5; this._marginRight = 5; this._marginBottom = 20; + + this._numDots = 40; + this._widgetHeight = 100; + this._widgetWidth = 0; + this._yAxisWidth = 55; + this._graphWidth = this._widgetWidth; + this._graphContentWidth = this._widgetWidth - 2 * this._yAxisWidth; + this._titleHeight = 20; + this._graphHeight = this._widgetHeight - this._titleHeight; + this._graphPaddingTop = 5; + this._xAxisHeight = 30; + this._graphContentHeight = + this._graphHeight - this._xAxisHeight - this._graphPaddingTop; + this._componentName = this._widget._componentName; + + this._startTime = 0; + this._endTime = 0; + this._primaryAxis = "ReqInCount"; + this._secondaryAxis = "AvgLatency"; + this._xScale = null; + this._primaryAxis = "ReqInCount"; + this._secondaryAxis = "AvgLatency"; + this._xScale = null; } + setComponentName(componentName: string) { + this._componentName = componentName; + console.log('Component Name set to:', this._componentName); + } + setCanvas(canvas: HTMLElement, tooltip: HTMLElement) { this._canvas = canvas; this._canvasWidth = this._canvas.offsetWidth; @@ -44,13 +101,20 @@ class ComponentView { this._taskRenderer.setCanvas(canvas, tooltip); this._xAxisDrawer.setCanvas(canvas); - d3.select(this._canvas) - .select("svg") + const svg = d3.select(this._canvas) + .select("svg") .attr("width", this._canvasWidth) .attr("height", this._canvasHeight); + + const svgElement = svg.node() as SVGElement; this._updateTimeScale(); this._renderData(); + + this._fetchAndRenderAxisData(svgElement, false); + if (!this._isSecondaryAxisSkipped()) { + this._fetchAndRenderAxisData(svgElement, true); + } } updateLayout() { @@ -63,19 +127,34 @@ class ComponentView { this._updateTimeScale(); } + setPrimaryAxis(axis: string) { + this._primaryAxis = axis; + console.log('Primary Axis set to:', this._primaryAxis); + } + + setSecondaryAxis(axis: string) { + this._secondaryAxis = axis; + console.log('Secondary Axis set to:', this._secondaryAxis); + } + setTimeAxis(startTime: number, endTime: number) { this._startTime = startTime; this._endTime = endTime; this._updateTimeScale(); + this._widget.setXAxis(startTime, endTime); + this._fetchAndRenderAxisData(this._svg, false); + if (!this._isSecondaryAxisSkipped()) { + this._fetchAndRenderAxisData(this._svg, true); + } } _updateTimeScale() { this._xScale = d3 - .scaleLinear() - .domain([this._startTime, this._endTime]) - .range([0, this._canvasWidth]); + .scaleLinear() + .domain([this._startTime, this._endTime]) + .range([0, this._canvasWidth - this._marginLeft - this._marginRight]); + this._taskRenderer.setXScale(this._xScale); - this._drawXAxis(); } @@ -100,12 +179,18 @@ class ComponentView { this._tasks = tasks; this._renderData(); - if (tasks.length > 0) { this._showLocation(tasks[0]); } else { this._removeLocation(); } + if (this._canvas) { + const svg = d3.select(this._canvas).select("svg").node() as SVGElement; + this._fetchAndRenderAxisData(svg, false); + if (!this._isSecondaryAxisSkipped()) { + this._fetchAndRenderAxisData(svg, true); + } + } } _renderData() { @@ -316,6 +401,180 @@ class ComponentView { } locationLabel.text(""); } + + setWidgetDimensions(width: number, height: number) { + this._widget.setDimensions(width, height); + } + + setTimeRange(startTime: number, endTime: number) { + this._widget.setXAxis(startTime, endTime); + this._updateTimeScale(); + } + + _fetchAndRenderAxisData(svg: SVGElement, isSecondary: boolean) { + const params = new URLSearchParams(); + if (isSecondary) { + params.set("info_type", this._secondaryAxis); + } else { + params.set("info_type", this._primaryAxis); + } + params.set("where", this._componentName); + console.log('Fetching data with componentName:', this._componentName); + params.set("start_time", this._startTime.toString()); + params.set("end_time", this._endTime.toString()); + console.log('Fetching data time', this._startTime.toString(), this._endTime.toString()); + params.set("num_dots", this._numDots.toString()); + console.log(params.toString()); + fetch(`/api/compinfo?${params.toString()}`) + .then((rsp) => rsp.json()) + .then((rsp) => { + console.log('After Fetching data with componentName:', this._componentName); + if (isSecondary) { + this._secondaryAxisData = rsp; + } else { + this._primaryAxisData = rsp; + } + this._renderAxisData(svg, rsp, isSecondary); + }) + .catch((error) => { + console.error('Error fetching data:', error); + }); + } + + + _renderAxisData(svg: SVGElement, data: object, isSecondary: boolean) { + const yScale = this._calculateYScale(data); + if (isSecondary) { + this._secondaryYScale = yScale; + } else { + this._primaryYScale = yScale; + } + + this._drawYAxis(svg, yScale, isSecondary); + this._renderDataCurve(svg, data, yScale, isSecondary); + } + + _calculateYScale(data: Object) { + let max = 0; + + data["data"].forEach((d: TimeValue) => { + if (d.value > max) { + max = d.value; + } + }); + + const yScale = d3 + .scaleLinear() + .domain([0, max]) + .range([this._canvasHeight - this._xAxisHeight, this._marginTop]); + + return yScale; + } + + _drawYAxis( + svg: SVGElement, + yScale: d3.ScaleLinear, + isSecondary: boolean + ) { + const canvas = d3.select(svg); + + let yAxis = d3.axisLeft(yScale); + let xOffset = this._yAxisWidth; + let axisClass = "y-axis-left"; + if (isSecondary) { + yAxis = d3.axisRight(yScale); + xOffset += this._graphContentWidth; + axisClass = "y-axis-right"; + } + + let yAxisGroup = canvas.select("." + axisClass); + if (yAxisGroup.empty()) { + yAxisGroup = canvas.append("g").attr("class", axisClass); + } + yAxisGroup.attr( + "transform", + `translate(${xOffset}, ${this._graphPaddingTop})` + ); + yAxisGroup.call(yAxis.ticks(5, ".1e")); + } + + _isPrimaryAxisSkipped() { + return this._primaryAxis === "-"; + } + + _isSecondaryAxisSkipped() { + return this._secondaryAxis === "-"; + } + + _renderDataCurve( + svg: SVGElement, + data: Object, + yScale: d3.ScaleLinear, + isSecondary: boolean + ) { + const canvas = d3.select(svg); + const className = `curve-${data["info_type"]}`; + let reqInGroup = canvas.select(`.${className}`); + if (reqInGroup.empty()) { + reqInGroup = canvas.append("g").attr("class", className); + } + + let color = "#d7191c"; + if (isSecondary) { + color = "#2c7bb6"; + } + + const pathData = []; + data["data"].forEach((d: TimeValue) => { + pathData.push([d.time, d.value]); + }); + + const line = d3 + .line() + .x((d) => this._yAxisWidth + this._xScale(d[0])) + .y((d) => this._graphPaddingTop + yScale(d[1])) + .curve(d3.curveCatmullRom.alpha(0.5)); + + let path = reqInGroup.select(".line"); + if (path.empty()) { + path = reqInGroup.append("path").attr("class", "line"); + } + path + .datum(pathData) + .attr("d", line) + .attr("fill", "none") + .attr("stroke", color); + + const circles = reqInGroup.selectAll("circle").data(data["data"]); + + const circleEnter = circles + .enter() + .append("circle") + .attr("cx", (d: TimeValue) => { + const x = this._yAxisWidth + this._xScale(d.time); + return x; + }) + .attr("cy", this._graphContentHeight + this._graphPaddingTop) + .attr("r", 2) + .attr("fill", color); + + circleEnter + .merge( + >( + circles + ) + ) + .transition() + .attr("cx", (d: TimeValue, i: number) => { + const x = this._yAxisWidth + this._xScale(d.time); + return x; + }) + .attr("cy", (d: TimeValue) => { + return this._graphPaddingTop + yScale(d.value); + }); + + circles.exit().remove(); + } } -export default ComponentView; +export default ComponentView; \ No newline at end of file diff --git a/daisen/static/src/taskpage.ts b/daisen/static/src/taskpage.ts index 02d8997..94e9bae 100644 --- a/daisen/static/src/taskpage.ts +++ b/daisen/static/src/taskpage.ts @@ -8,6 +8,8 @@ import XAxisDrawer from "./xaxisdrawer"; import { ZoomHandler } from "./mouseeventhandler"; import { Task } from "./task"; import { smartString } from "./smartvalue"; +import { Widget, TimeValue } from "./widget"; +import Dashboard from "./dashboard"; export class TaskPage implements ZoomHandler { _container: HTMLElement; @@ -29,7 +31,7 @@ export class TaskPage implements ZoomHandler { _yIndexAssigner: TaskYIndexAssigner; _taskView: TaskView; _componentView: ComponentView; - + _widget: Widget; constructor() { this._container = null; this._taskViewCanvas = null; @@ -42,6 +44,7 @@ export class TaskPage implements ZoomHandler { this._legendCanvas = null; this._componentOnlyMode = false; this._componentName = ""; + this._widget = null; this._currTasks = { task: null, @@ -56,6 +59,9 @@ export class TaskPage implements ZoomHandler { this._taskColorCoder = new TaskColorCoder(); this._legend = new Legend(this._taskColorCoder, this); this._yIndexAssigner = new TaskYIndexAssigner(); + const widgetCanvas = document.createElement('div'); + document.body.appendChild(widgetCanvas); + this._widget = new Widget(this._componentName, widgetCanvas, new Dashboard()); this._taskView = new TaskView( this._yIndexAssigner, new TaskRenderer(this, this._taskColorCoder), @@ -64,8 +70,13 @@ export class TaskPage implements ZoomHandler { this._componentView = new ComponentView( this._yIndexAssigner, new TaskRenderer(this, this._taskColorCoder), - new XAxisDrawer() + new XAxisDrawer(), + this._widget ); + this._componentView.setComponentName(this._componentName); + this._componentView.setPrimaryAxis('ReqInCount'); + this._componentView.setSecondaryAxis('AvgLatency'); + this._componentView.setTimeAxis(this._startTime, this._endTime); } _handleMouseMove(e: MouseEvent) { @@ -296,8 +307,10 @@ export class TaskPage implements ZoomHandler { async showComponent(name: string) { this._componentName = name; + this._componentView.setComponentName(name); + console.log('TaskPage', this._componentName); this._switchToComponentOnlyMode(); - + await this._waitForComponentNameUpdate(); const rsps = await Promise.all([ fetch( `/api/trace?` + @@ -310,8 +323,16 @@ export class TaskPage implements ZoomHandler { this._taskColorCoder.recode(sameLocationTasks); this._legend.render(); + console.log('ComponentView Component Name before render:', this._componentView._componentName); + await this._componentView.render(sameLocationTasks); + } - this._componentView.render(sameLocationTasks); + _waitForComponentNameUpdate() { + return new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 1000); + }); } _switchToComponentOnlyMode() { diff --git a/daisen/static/src/widget.ts b/daisen/static/src/widget.ts index 484467d..b7de064 100644 --- a/daisen/static/src/widget.ts +++ b/daisen/static/src/widget.ts @@ -3,7 +3,7 @@ import Dashboard from "./dashboard"; import TaskPage from "./taskpage"; import { ZoomHandler, MouseEventHandler } from "./mouseeventhandler"; -class TimeValue { +export class TimeValue { time: number; value: number; @@ -13,7 +13,12 @@ class TimeValue { } } -class Widget implements ZoomHandler { +type DataObject = { + info_type: string; + data: TimeValue[]; +}; + +export class Widget implements ZoomHandler { _dashboard: Dashboard; _componentName: string; _div: HTMLDivElement; @@ -44,6 +49,7 @@ class Widget implements ZoomHandler { _graphPaddingTop: number; _xScale: d3.ScaleLinear; + _yScale: d3.ScaleLinear; _primaryYScale: d3.ScaleLinear; _secondaryYScale: d3.ScaleLinear; @@ -76,9 +82,36 @@ class Widget implements ZoomHandler { this._xScale = null; } + setSVG(svg: SVGElement) { + this._svg = svg; + } + + public async initialize(): Promise { + this._svg = await this.loadSvgElement(); + } + + private async loadSvgElement(): Promise { + return new Promise(resolve => { + setTimeout(() => { + const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svgElement.setAttribute("width", "100"); + svgElement.setAttribute("height", "50"); + resolve(svgElement); + }, 1000); + }); + } + setDimensions(width: number, height: number) { + this._widgetWidth = width; + this._widgetHeight = height; + this._graphWidth = this._widgetWidth; + this._graphContentWidth = this._widgetWidth - 2 * this._yAxisWidth; + this._graphHeight = this._widgetHeight - this._titleHeight; + this._graphContentHeight = this._graphHeight - + this._xAxisHeight - this._graphPaddingTop; + } + resize(width: number, height: number) { - this._setWidgetWidth(width); - this._setWidgetHeight(height); + this.setDimensions(width, height); this._renderXAxis(this._svg); if (!this._isPrimaryAxisSkipped()) { this._renderDataCurve( @@ -89,7 +122,7 @@ class Widget implements ZoomHandler { ); this._drawYAxis(this._svg, this._primaryYScale, false); } - + if (!this._isSecondaryAxisSkipped()) { this._renderDataCurve( this._svg, @@ -119,6 +152,10 @@ class Widget implements ZoomHandler { .range([0, this._graphContentWidth]); } + setYScale(yScale: d3.ScaleLinear) { + this._yScale = yScale; + } + temporaryTimeShift(startTime: number, endTime: number) { this.setXAxis(startTime, endTime); this._renderXAxis(this._svg); @@ -199,6 +236,10 @@ class Widget implements ZoomHandler { this._fetchAndRenderAxisData(svg, false); } + setXScale(xScale: d3.ScaleLinear) { + this._xScale = xScale; + } + createWidget(width: number, height: number) { const div = document.createElement("div"); @@ -253,7 +294,7 @@ class Widget implements ZoomHandler { } _createSVG(div: HTMLDivElement) { - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") as SVGSVGElement;; svg.setAttribute("width", "100%"); svg.setAttribute("height", this._widgetHeight.toString()); div.appendChild(svg); @@ -295,7 +336,6 @@ class Widget implements ZoomHandler { params.set("start_time", this._startTime.toString()); params.set("end_time", this._endTime.toString()); params.set("num_dots", this._numDots.toString()); - fetch(`/api/compinfo?${params.toString()}`) .then((rsp) => rsp.json()) .then((rsp) => {