Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/src/components/FrameItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export default function FrameItem({
>
<FrameHeader
frame={frame}
tabResult={tabResult}
isActive={activeFrameId === frame.id}
isFullscreen={isFullscreen}
collapsed={collapsed}
Expand Down
84 changes: 35 additions & 49 deletions client/src/components/FrameLayout/FrameHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,23 @@ import { useDispatch } from 'react-redux'

import { discardFrame, setActiveFrame } from 'actions/frames'
import { updateQueryAndAction, updateQueryVars } from 'actions/query'
import { latencyBarSegments, latencyTooltip, timeToText } from 'lib/latency'

import LatencyModal from './LatencyModal'
import QueryPreview from './QueryPreview'
import SharingSettings from './SharingSettings'
import './FrameHeader.scss'

function timeToText(ns) {
if (ns === null || ns === undefined) {
return ''
}
if (ns < 1e4) {
return ns.toFixed(0) + 'ns'
}
const ms = ns / 1e6
if (ms < 1000) {
return ms.toFixed(0) + 'ms'
}
const s = ms / 1000
if (s <= 60) {
return s.toFixed(1) + 's'
}
const secondsOnly = Math.round(s) % 60

return `${Math.floor(s / 60)}m${secondsOnly.toLocaleString('en', {
minimumIntegerDigits: 2,
})}s`
}

export default function FrameHeader({
collapsed,
frame,
tabResult,
isActive,
isFullscreen,
onToggleFullscreen,
}) {
const dispatch = useDispatch()
const [showLatency, setShowLatency] = React.useState(false)
const selectFrame = () => {
dispatch(updateQueryAndAction(frame.query, frame.action))
if (frame.action === 'query') {
Expand All @@ -54,40 +36,40 @@ export default function FrameHeader({
dispatch(setActiveFrame(frame.id))
}

function drawLatency(serverNs, networkNs) {
if (
serverNs === undefined ||
networkNs === undefined ||
serverNs === null ||
networkNs === null
) {
function drawLatency(result) {
if (!result) {
return null
}
const segments = latencyBarSegments(
result.serverLatency,
result.networkLatencyNs,
)
if (!segments.length) {
return null
}
const ratio = serverNs / (serverNs + networkNs)

const title = `Alpha Latency: ${timeToText(serverNs)} (${(
ratio * 100
).toFixed(0)}%)\nNetwork Latency: ${timeToText(networkNs)} (${(
(1 - ratio) * 100
).toFixed(0)}%)\nTotal Latency: ${timeToText(serverNs + networkNs)}`
const totalNs = segments.reduce((sum, s) => sum + s.ns, 0)

const flexStyles = {
server: { flexGrow: 1000 * ratio },
network: { flexGrow: 1000 * (1 - ratio) },
}
return (
<div className='timing-outer' title={title} onClick={selectFrame}>
<div
className='timing-outer'
title={`${latencyTooltip(segments)}\n(click for full breakdown)`}
onClick={(e) => {
e.stopPropagation()
setShowLatency(true)
}}
>
<div className='progress'>
<div className='server-bar' style={flexStyles.server} />
<div className='network-bar' style={flexStyles.network} />
{segments.map((s) => (
<div
key={s.key}
className={`latency-seg latency-seg--${s.key.replace(/_ns$/, '')}`}
style={{ flexGrow: Math.max(1, 1000 * s.ratio) }}
/>
))}
</div>
<div className='text-wrapper'>
<div className='server-text' style={flexStyles.server}>
{timeToText(serverNs)}
</div>
<div className='network-text' style={flexStyles.network}>
{timeToText(networkNs)}
</div>
<div className='server-text'>{timeToText(totalNs)}</div>
</div>
</div>
)
Expand All @@ -109,7 +91,7 @@ export default function FrameHeader({
/>
) : null}

{drawLatency(frame.serverLatencyNs, frame.networkLatencyNs)}
{drawLatency(tabResult)}

<div className='actions'>
{collapsed ? null : (
Expand Down Expand Up @@ -143,6 +125,10 @@ export default function FrameHeader({
</button>
) : null}
</div>

{showLatency && tabResult && (
<LatencyModal result={tabResult} onHide={() => setShowLatency(false)} />
)}
</div>
)
}
21 changes: 16 additions & 5 deletions client/src/components/FrameLayout/FrameHeader.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,26 @@
display: flex;
flex-direction: row;

.server-bar {
.latency-seg {
flex-basis: 2px;
height: 2px;
background-color: $serverColor;
}

.network-bar {
flex-basis: 2px;
height: 2px;
// Server phases, in pipeline order.
.latency-seg--parsing {
background-color: color.adjust(#5cb85c, $saturation: -30%);
}
.latency-seg--processing {
background-color: $serverColor;
}
.latency-seg--encoding {
background-color: color.adjust(#d9534f, $saturation: -30%);
}
.latency-seg--assign_timestamp {
background-color: color.adjust(#9b59b6, $saturation: -30%);
}
.latency-seg--network {
background-color: $networkColor;
}
}
Expand Down Expand Up @@ -97,7 +108,7 @@

*,
& {
cursor: default;
cursor: pointer;
}
}

Expand Down
129 changes: 129 additions & 0 deletions client/src/components/FrameLayout/LatencyModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react'
import Modal from 'react-bootstrap/Modal'

import { latencyBarSegments, numUidSegments, timeToText } from 'lib/latency'

import './LatencyModal.scss'

// Phase colors mirror the inline latency bar (.latency-seg--* in
// FrameHeader.scss).
const PHASE_COLOR = {
parsing: '#74b074',
processing: '#e0a960',
encoding: '#cc6b68',
assign_timestamp: '#a974c0',
network: '#5b8bb5',
}

// Segment keys for server phases carry an "_ns" suffix (parsing_ns, ...);
// network does not. Normalize before looking up the color.
const colorFor = (key) => PHASE_COLOR[key.replace(/_ns$/, '')] || '#5b8bb5'

export default function LatencyModal({ result, onHide }) {
const segments = latencyBarSegments(
result.serverLatency,
result.networkLatencyNs,
)
const totalNs = segments.reduce((sum, s) => sum + s.ns, 0)

// Lay the phases out as a timeline/waterfall: each bar starts where the
// previous phase ended, so the row reads as a sequence over the total
// duration rather than five independent bars from zero.
let elapsedNs = 0
const timeline = segments.map((s) => {
const offset = totalNs > 0 ? elapsedNs / totalNs : 0
elapsedNs += s.ns
return { ...s, offset }
})

const { segments: uidSegments, total: uidTotal } = numUidSegments(
result.response && result.response.data,
)

return (
<Modal
centered
show={true}
size='lg'
onHide={onHide}
dialogClassName='latency-modal'
>
<Modal.Header closeButton>
<Modal.Title>Query latency breakdown</Modal.Title>
</Modal.Header>
<Modal.Body>
{segments.length === 0 ? (
<p className='latency-modal__empty'>
No latency data for this query.
</p>
) : (
<div className='latency-modal__section'>
<div className='latency-modal__section-title'>Latency</div>
{timeline.map((s) => (
<div className='latency-modal__row' key={s.key}>
<span className='latency-modal__label'>{s.label}</span>
<span className='latency-modal__track'>
<span
className='latency-modal__bar latency-modal__bar--timeline'
style={{
left: `${s.offset * 100}%`,
width: `${Math.max(s.ratio * 100, 0.5)}%`,
backgroundColor: colorFor(s.key),
}}
/>
</span>
<span className='latency-modal__value'>{s.text}</span>
<span className='latency-modal__pct'>
{(s.ratio * 100).toFixed(0)}%
</span>
</div>
))}
<div className='latency-modal__row latency-modal__row--total'>
<span className='latency-modal__label'>Total</span>
<span className='latency-modal__track latency-modal__track--rule' />
<span className='latency-modal__value'>
{timeToText(totalNs)}
</span>
<span className='latency-modal__pct' />
</div>
</div>
)}

{uidSegments.length > 0 && (
<div className='latency-modal__section'>
<div className='latency-modal__section-title'>
Num UIDs
<span className='latency-modal__section-total'>
total: {uidTotal.toLocaleString()}
</span>
</div>
{uidSegments.map((s) => (
<div className='latency-modal__row' key={s.key}>
<span className='latency-modal__label' title={s.key}>
{s.key}
</span>
<span className='latency-modal__track'>
<span
className='latency-modal__bar'
style={{
width: `${Math.max(s.ratio * 100, 0.5)}%`,
backgroundColor: '#5b8bb5',
}}
/>
</span>
<span className='latency-modal__value'>
{s.count.toLocaleString()}
</span>
</div>
))}
</div>
)}
</Modal.Body>
</Modal>
)
}
Loading
Loading