Skip to content

Commit

Permalink
Improve X axis "delta time" labeling
Browse files Browse the repository at this point in the history
The Plotly graphing package doesn't directly support a "delta time" type, and
in the comparison view we want to use delta time to compare two runs that will
generally have different absolute timestamps. (It turns out that the native
PatternFly graphing package, Victory, has the same limitation.)

Initially, this just reported numeric delta seconds, but that's unnatural for
a reader. This PR adds support for a `absolute_relative` option which reports
the delta times as small absolute timestamps, like `1970-01-01 00:01:00` for
60 seconds, formatting ticks using `"%H:%M:%S"` ("00:01:00") for readability.

I also made the X axis title appear, which necessitated some refactoring of
the layout to avoid overlaying the legend on the axis label; and in the
process I moved the "presentation specific" width parameter into the UI and
the others into the API so they don't have to be duplicated in the two action
calls.
  • Loading branch information
dbutenhof committed Nov 18, 2024
1 parent ac58188 commit e0daae0
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 31 deletions.
77 changes: 65 additions & 12 deletions backend/app/services/crucible_svc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from datetime import datetime, timezone
from typing import Any, Iterator, Optional, Tuple, Union

from elasticsearch import AsyncElasticsearch, NotFoundError
from elasticsearch import AsyncElasticsearch
from fastapi import HTTPException, status
from pydantic import BaseModel

Expand Down Expand Up @@ -67,16 +67,24 @@ class GraphList(BaseModel):
Normally the X axis will be the actual sample timestamp values; if you
specify relative=True, the X axis will be the duration from the first
timestamp of the metric series. This allows graphs of similar runs started
at different times to be overlaid.
at different times to be overlaid. Plotly (along with other plotting
packages like PatternFly's Victory) doesn't support a "delta time" axis
unit, so also specifying absolute_relative will report relative times as
small absolute times (e.g., "1970-01-01 00:00:01" for 1 second) and a
"tick format" of "%H:%M:%S", which will look nice on the graph as long as
the total duration doesn't reach 24 hours. Without absolute_relative, the
duration is reported as numeric (floating point) seconds.
Fields:
name: Specify a name for the set of graphs
relative: True for relative timescale
absolute_relative: True to report relative timestamps as absolute
graphs: a list of Graph objects
"""

name: str
relative: bool = False
absolute_relative: bool = False
graphs: list[Metric]


Expand Down Expand Up @@ -1869,7 +1877,42 @@ async def get_metrics_graph(self, graphdata: GraphList) -> dict[str, Any]:
"""
start = time.time()
graphlist = []
layout: dict[str, Any] = {"width": "1500"}
if graphdata.relative:
if graphdata.absolute_relative:
x_label = "sample runtime (HH:MM:SS)"
format = "%H:%M:%S"
else:
x_label = "sample runtime (seconds)"
format = None
else:
x_label = "sample timestamp"
format = "%Y:%M:%d %X %Z"
xaxis = {
"title": {
"text": x_label,
"font": {"color": "gray", "variant": "petite-caps", "weight": 1000},
},
}
if format:
xaxis["type"] = "date"
xaxis["tickformat"] = format
layout: dict[str, Any] = {
"showlegend": True,
"responsive": True,
"autosize": True,
"xaxis_title": x_label,
"yaxis_title": "Metric value",
"xaxis": xaxis,
"legend": {
"xref": "container",
"yref": "container",
"xanchor": "right",
"yanchor": "top",
"x": 0.9,
"y": 1,
"orientation": "h",
},
}
axes = {}
yaxis = None
cindex = 0
Expand All @@ -1891,6 +1934,9 @@ async def get_metrics_graph(self, graphdata: GraphList) -> dict[str, Any]:
run_id = g.run
names = g.names
metric: str = g.metric
run_idx = None
if len(run_id_list) > 1:
run_idx = f"Run {run_id_list.index(run_id) + 1}"

# The caller can provide a title for each graph; but, if not, we
# journey down dark overgrown pathways to fabricate a default with
Expand Down Expand Up @@ -1972,13 +2018,17 @@ async def get_metrics_graph(self, graphdata: GraphList) -> dict[str, Any]:
if graphdata.relative:
if not first:
first = p.begin
s = (p.begin - first) / 1000.0
e = (p.end - first) / 1000.0
if graphdata.absolute_relative:
s = self._format_timestamp(p.begin - first)
e = self._format_timestamp(p.end - first)
else:
s = (p.begin - first) / 1000
e = (p.end - first) / 1000
x.extend([s, e])
else:
x.extend(
[self._format_timestamp(p.begin), self._format_timestamp(p.end)]
)
s = self._format_timestamp(p.begin)
e = self._format_timestamp(p.end)
x.extend([s, e])
y.extend([p.value, p.value])
y_max = max(y_max, p.value)

Expand All @@ -1996,12 +2046,15 @@ async def get_metrics_graph(self, graphdata: GraphList) -> dict[str, Any]:
"type": "scatter",
"mode": "line",
"marker": {"color": color},
"labels": {
"x": "sample timestamp",
"y": "samples / second",
},
}

if run_idx:
graphitem["legendgroup"] = run_idx
graphitem["legendgrouptitle"] = {
"text": run_idx,
"font": {"variant": "small-caps", "style": "italic"},
}

# Y-axis scaling and labeling is divided by benchmark label;
# so store each we've created to reuse. (E.g., if we graph
# 5 different mpstat::Busy-CPU periods, they'll share a single
Expand Down
20 changes: 3 additions & 17 deletions frontend/src/actions/ilabActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,7 @@ export const fetchGraphData = (uid) => async (dispatch, getState) => {
graphs,
});
if (response.status === 200) {
response.data.layout["showlegend"] = true;
response.data.layout["responsive"] = "true";
response.data.layout["autosize"] = "true";
response.data.layout["legend"] = {
orientation: "h",
xanchor: "left",
yanchor: "top",
y: -0.1,
};
response.data.layout["width"] = 1500;
copyData.push({
uid,
data: response.data.data,
Expand Down Expand Up @@ -334,17 +326,11 @@ export const fetchMultiGraphData = (uids) => async (dispatch, getState) => {
const response = await API.post(`/api/v1/ilab/runs/multigraph`, {
name: "comparison",
relative: true,
absolute_relative: true,
graphs,
});
if (response.status === 200) {
response.data.layout["showlegend"] = true;
response.data.layout["responsive"] = "true";
response.data.layout["autosize"] = "true";
response.data.layout["legend"] = {
orientation: "h",
xanchor: "left",
yanchor: "top",
};
response.data.layout["width"] = 1500;
const graphData = [];
graphData.push({
data: response.data.data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const IlabCompareComponent = () => {
type={"ilab"}
/>
</div>
<Stack>
<Stack hasGutter>
<StackItem span={12} className="metrics-select">
<MetricsSelect ids={selectedItems} />
</StackItem>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/templates/ILab/IlabExpandedRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const IlabRowContent = (props) => {
>
<div>Metrics:</div>
<MetricsSelect ids={[item.id]} />
<Stack>
<Stack hasGutter>
<StackItem key={uid()} id="summary" className="summary-card">
<ILabSummary ids={[item.id]} />
</StackItem>
Expand Down

0 comments on commit e0daae0

Please sign in to comment.