Skip to content
Merged
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
10 changes: 3 additions & 7 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
@open-serial-number="openSerialNumber=true"
@open-wifi="openWifi=true"
/>
<w-dialog v-model="openMetrics" title="Board metrics" :width="820" persistent-no-animation>
<TheMetrics :active="openMetrics" :revision="recore_revision" />
</w-dialog>
<w-card class="mxa pa3 card secondary">
<w-flex wrap class="text-center">
<div class="xs5 pa1">
Expand Down Expand Up @@ -53,7 +50,6 @@
:revision="recore_revision"
:serialNumber="serial_number"
:network="network"
@open-metrics="openMetrics = true"
/>
</div>
<div class="xs1 pa1 align-self-center">
Expand Down Expand Up @@ -105,20 +101,23 @@
<ProgressBar
ref="transferprogressbar"
v-show="state == 'DOWNLOADING' || state == 'UPLOADING'"
:revision="recore_revision"
/>
<span class="red">{{ this.computeSizeCheckText() }}</span>
</div>
<div class="xs1 pa1">
<ProgressBar
ref="magicprogressbar"
v-show="state === 'MAGIC' || state === 'UPLOADING_MAGIC'"
:revision="recore_revision"
/>
{{ this.options.magicmode ? "" : "Choose image to install" }}
</div>
<div class="xs1 pa1">
<ProgressBar
ref="installprogressbar"
v-show="state == 'INSTALLING' || state == 'BACKUPING'"
:revision="recore_revision"
/>
</div>
<div class="xs1 pa1">
Expand Down Expand Up @@ -221,7 +220,6 @@
import TheOptions from "./components/TheOptions";
import TheLogger from "./components/TheLogger";
import TheInfo from "./components/TheInfo";
import TheMetrics from "./components/TheMetrics";
import ProgressBar from "./components/ProgressBar";
import FlashSelector from "./components/FlashSelector";
import IntegrityChecker from "./components/IntegrityChecker";
Expand All @@ -238,7 +236,6 @@ export default {
TheOptions,
TheLogger,
TheInfo,
TheMetrics,
ProgressBar,
FlashSelector,
IntegrityChecker,
Expand Down Expand Up @@ -275,7 +272,6 @@ export default {
localImages: [],
uploadError: false,
openInfo: false,
openMetrics: false,
openLog: false,
openOptions: false,
showOverlay: false,
Expand Down
149 changes: 35 additions & 114 deletions client/src/components/ProgressBar.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<template>
<div>
<div class="metrics-hover" @mouseleave="onLeave">
<!-- The bar owns the hover interaction, so the popup is anchored to the
thing it annotates rather than to the page. -->
<div
ref="bar"
class="bar-hover"
@mouseenter="onEnter"
@mousemove="onMove"
@mouseleave="onLeave"
@click="togglePin">
<w-progress
:model-value="progress.progress"
Expand All @@ -20,99 +20,45 @@

<w-flex justify-space-between class="wrapper">
<div class="align-self-start">{{minutes}}m:{{seconds}}s</div>
<!-- Instantaneous only. Peak and average belong with the trace they
summarise, in the popup - on the line they competed with the number
beside them and left three figures to read where one was wanted. -->
<!-- Instantaneous only. The hover panel holds the historical board
readings, leaving this timing line to answer the usual at-a-glance
transfer question. -->
<div class="align-self-center">{{bandwidth}} MB/s</div>
<div class="align-self-end">{{minutesR}}m:{{secondsR}}s</div>
</w-flex>

<!-- Throughput history. The MB/s figure above is instantaneous (bytes moved
between two polls) and therefore jumpy, which makes a genuine stall hard
to tell apart from normal jitter. This keeps the last few minutes so a
flat stretch is obvious while it is happening.

Out of the layout and into a popup (#122): inline it overhung the
progress bar, collided with the timing line under it, and was too narrow
to read - and it was on screen permanently even though the numbers
beside it already answer the usual question. Opt-in suits it better:
whoever wants the shape of the transfer goes looking for it.

position: fixed so it cannot be clipped by an ancestor's overflow. -->
<!-- The same hover that previously exposed a throughput trace now exposes
the complete board state. It is a descendant of .metrics-hover, so the
popup stays open while the pointer moves from the bar onto a graph. -->
<div
v-if="plotVisible"
class="speed-popup"
v-if="metricsVisible"
class="metrics-popup"
:style="{ left: popupLeft + 'px', top: popupTop + 'px' }">
<div class="speed-popup-title">
throughput <span class="peak">peak {{peak}} · avg {{average}} MB/s</span>
</div>
<svg
class="sparkline"
:viewBox="'0 0 ' + MAX_SAMPLES + ' 24'"
preserveAspectRatio="none">
<polygon :points="areaPoints" fill="rgba(3, 169, 244, 0.25)" />
<polyline
:points="linePoints"
fill="none"
stroke="#03a9f4"
stroke-width="0.7"
vector-effect="non-scaling-stroke" />
</svg>
<div v-if="pinned" class="speed-popup-hint">tap the bar again to hide</div>
<div class="metrics-popup-title">Board metrics</div>
<TheMetrics :active="metricsVisible" :revision="revision" />
<div v-if="pinned" class="metrics-popup-hint">tap the bar again to hide</div>
</div>
</div>
</template>

<script>
import { mapGetters } from 'vuex';
import TheMetrics from './TheMetrics.vue';

// At roughly one sample per poll (~1s) this is a few minutes of history,
// which is the timescale the eMMC-backpressure stalls play out over.
const MAX_SAMPLES = 180;

// Popup geometry, in px. Wide enough that the trace has a shape rather than a
// few spikes, which was the main complaint about the inline version.
const POPUP_W = 280;
const POPUP_H = 96;
// Popup geometry, in px. This matches the former metrics dialog width while
// keeping the panel inside a small display and above or below the transfer bar.
const POPUP_W = 820;
const POPUP_H = 420;
const CURSOR_GAP = 14;

export default {
name: 'ProgressBar',
components: { TheMetrics },
props: { revision: String },
computed: {
...mapGetters(['progress']),
// Scale to the tallest sample so the trace uses the full height, with a
// floor so that an all-zero history renders flat along the bottom
// instead of dividing by zero.
scale: function() {
return Math.max(...this.history, 0.1);
},
peak: function() {
return Math.max(...this.history, 0).toFixed(1);
},
// Mean over the whole plotted window, stalls included. Averaging only the
// moving samples would report a rate the transfer never achieved, and
// hiding the stalls is the opposite of what this plot is for.
average: function() {
if (this.history.length === 0) {
return '0.0';
}
return (this.history.reduce((a, b) => a + b, 0) / this.history.length).toFixed(1);
},
// Nothing to show until there are two points to draw a line between.
plotVisible: function() {
return (this.hovering || this.pinned) && this.history.length > 1;
},
linePoints: function() {
// Right-aligned, so the newest sample sits at the right edge and the
// trace grows leftwards instead of stretching as history fills up.
const offset = MAX_SAMPLES - this.history.length;
return this.history
.map((v, i) => (offset + i) + ',' + (24 - (v / this.scale) * 23).toFixed(2))
.join(' ');
},
areaPoints: function() {
const offset = MAX_SAMPLES - this.history.length;
return offset + ',24 ' + this.linePoints + ' ' + MAX_SAMPLES + ',24';
metricsVisible: function() {
return this.hovering || this.pinned;
}
},
data: () => ({
Expand All @@ -121,28 +67,24 @@ export default {
secondsR: 0,
minutesR: 0,
bandwidth: 0,
history: [],
hovering: false,
pinned: false,
popupLeft: 0,
popupTop: 0,
MAX_SAMPLES
}),
methods: {
reset: function() {
this.history = [];
this.hovering = false;
this.pinned = false;
},
// Follow the cursor on X only, and sit at a fixed height above the bar.
// Following both axes reads as a tooltip but makes the trace bob while you
// are trying to read it; pinning Y to the bar keeps it steady and still
// tracks the pointer in the direction that matters.
// Pinning Y to the bar keeps the larger panel steady while it is read.
place: function(evt) {
const bar = evt.currentTarget.getBoundingClientRect();
const maxLeft = window.innerWidth - POPUP_W - 8;
const bar = this.$refs.bar.getBoundingClientRect();
const width = Math.min(POPUP_W, window.innerWidth - 16);
const maxLeft = window.innerWidth - width - 8;
// Flip rather than overflow at the right edge.
this.popupLeft = Math.max(8, Math.min(evt.clientX - POPUP_W / 2, maxLeft));
this.popupLeft = Math.max(8, Math.min(evt.clientX - width / 2, maxLeft));
// Above the bar by default; below it if there is no room above.
const above = bar.top - POPUP_H - CURSOR_GAP;
this.popupTop = above >= 8 ? above : bar.bottom + CURSOR_GAP;
Expand All @@ -160,8 +102,7 @@ export default {
this.hovering = false;
},
// Touch has no hover. The board is used from a touchscreen as well as a
// desktop browser, so tapping the bar pins the popup open - otherwise the
// plot would simply not exist on the panel (#122).
// desktop browser, so tapping the bar pins the metrics panel open.
togglePin: function(evt) {
this.pinned = !this.pinned;
if (this.pinned) {
Expand All @@ -176,18 +117,6 @@ export default {
let progress = model.progress/100;
this.bandwidth = model.bandwidth.toFixed(1);

// Clamp: the server derives bandwidth from a byte-count delta, which
// goes negative whenever the counter restarts (a new operation, or the
// magic path switching from upload bytes to flash bytes).
let sample = model.bandwidth;
if (!isFinite(sample) || sample < 0) {
sample = 0;
}
this.history.push(sample);
if (this.history.length > MAX_SAMPLES) {
this.history.shift();
}

let secondsTotal = (timePassedSeconds/progress);
let timeFinished = new Date(new Date(model.timeStarted).getTime() + secondsTotal*1000);
let timeRemaining = (timeFinished - Date.now())/1000;
Expand All @@ -209,35 +138,27 @@ export default {
padding: 0.35em 0;
cursor: crosshair;
}
.speed-popup {
.metrics-popup {
position: fixed;
width: 280px;
height: 96px;
width: min(820px, calc(100vw - 16px));
max-height: min(420px, calc(100vh - 16px));
overflow-y: auto;
z-index: 1000;
padding: 0.4em 0.6em;
border-radius: 6px;
background: rgba(20, 20, 20, 0.92);
color: #eee;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
pointer-events: none;
pointer-events: auto;
}
.speed-popup-title {
.metrics-popup-title {
font-size: 0.75em;
opacity: 0.8;
margin-bottom: 0.2em;
}
.speed-popup-hint {
.metrics-popup-hint {
font-size: 0.65em;
opacity: 0.5;
text-align: center;
}
.sparkline {
width: 100%;
height: 3.2em;
display: block;
}
.peak {
opacity: 0.6;
font-size: 0.85em;
}
</style>
14 changes: 0 additions & 14 deletions client/src/components/TheInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@
>hotspot</span
><span v-if="line.active" class="badge" title="Carries traffic (default route)">active</span>
</template>
<!-- Lives here rather than in the icon row: the metrics are a detail
about this board, like the revision and serial number above, not a
fourth top-level action beside Log, Info and Options. -->
<br />
<w-button class="metrics-link" @click="$emit('open-metrics')" text sm>
Board metrics&hellip;
</w-button>
</p>
</w-transition-expand>
</template>
Expand All @@ -39,7 +32,6 @@ import { networkLines } from "../network";

export default {
name: "TheInfo",
emits: ["open-metrics"],
props: {
open: Boolean,
version: String,
Expand Down Expand Up @@ -79,12 +71,6 @@ export default {
.signal i.on {
opacity: 1;
}
/* Left-aligned with the lines above it, so it reads as part of the list rather
than as a floating control. */
.metrics-link {
margin: 6px 0 0 -8px;
text-transform: none;
}
.badge {
margin-left: 6px;
padding: 0 5px;
Expand Down
1 change: 1 addition & 0 deletions client/src/components/TheMetrics.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export default {
warnAbove: 0, floor: 0, ceiling: 7,
note: "capping cpufreq for heat" }),
this.panel({ key: "cpu_freq", label: "CPU frequency", unit: "MHz", digits: 0 }),
this.panel({ key: "dram_freq", label: "DRAM frequency", unit: "MHz", digits: 0 }),
this.panel({
key: "vcc_dram", label: "DRAM rail", unit: "V", digits: 2,
note: dram ? `expected ${dram.label}` : null,
Expand Down
Loading
Loading