diff --git a/src/cargo/core/compiler/timings.js b/src/cargo/core/compiler/timings.js
index 170827363a2..28767db845c 100644
--- a/src/cargo/core/compiler/timings.js
+++ b/src/cargo/core/compiler/timings.js
@@ -163,9 +163,8 @@ function render_timing_graph() {
   for (c of CONCURRENCY_DATA) {
     max_v = Math.max(max_v, c.active, c.waiting, c.inactive);
   }
-  let [step, top] = split_ticks(max_v, GRAPH_HEIGHT / MIN_TICK_DIST);
-  let num_ticks = top / step;
-  let tick_dist = GRAPH_HEIGHT / num_ticks;
+  const px_per_v = GRAPH_HEIGHT / max_v;
+  const {step, tick_dist, num_ticks} = split_ticks(max_v, px_per_v, GRAPH_HEIGHT);
   ctx.textAlign = 'end';
   for (n=0; n<num_ticks; n++) {
     let y = HEIGHT - Y_LINE - ((n + 1) * tick_dist);
@@ -299,7 +298,7 @@ function draw_graph_axes(id, graph_height) {
   // 4096 is still ridiculously large, and probably won't render on mobile
   // browsers, but should be ok for many desktop environments.
   const graph_width = Math.min(scale * DURATION, 4096);
-  const px_per_sec = Math.floor(graph_width / DURATION);
+  const px_per_sec = graph_width / DURATION;
   const canvas_width = Math.max(graph_width + X_LINE + 30, X_LINE + 250);
   const canvas_height = graph_height + MARGIN + Y_LINE;
   let ctx = setup_canvas(id, canvas_width, canvas_height);
@@ -318,9 +317,7 @@ function draw_graph_axes(id, graph_height) {
   ctx.stroke();
 
   // Draw X tick marks.
-  const [step, top] = split_ticks(DURATION, graph_width / MIN_TICK_DIST);
-  const num_ticks = top / step;
-  const tick_dist = graph_width / num_ticks;
+  const {step, tick_dist, num_ticks} = split_ticks(DURATION, px_per_sec, graph_width);
   ctx.fillStyle = '#303030';
   for (let n=0; n<num_ticks; n++) {
     const x = X_LINE + ((n + 1) * tick_dist);
@@ -347,40 +344,39 @@ function draw_graph_axes(id, graph_height) {
   return {canvas_width, canvas_height, graph_width, graph_height, ctx, px_per_sec};
 }
 
-function round_up(n, step) {
-  if (n % step == 0) {
-    return n;
-  } else {
-    return (step - n % step) + n;
+// Determine the spacing and number of ticks along an axis.
+function split_ticks(max_value, px_per_v, max_px) {
+  const max_ticks = Math.floor(max_px / MIN_TICK_DIST);
+  if (max_ticks <= 1) {
+    // Graph is too small for even 1 tick.
+    return {step: max_value, tick_dist: max_px, num_ticks: 1};
   }
-}
-
-// Determine the `(step, max_value)` of the number of ticks along an axis.
-function split_ticks(n, max_ticks) {
-  max_ticks = Math.ceil(max_ticks);
-  if (n <= max_ticks) {
-    return [1, n];
-  } else if (n <= max_ticks * 2) {
-    return [2, round_up(n, 2)];
-  } else if (n <= max_ticks * 4) {
-    return [4, round_up(n, 4)];
-  } else if (n <= max_ticks * 5) {
-    return [5, round_up(n, 5)];
+  let step;
+  if (max_value <= max_ticks) {
+    step = 1;
+  } else if (max_value <= max_ticks * 2) {
+    step = 2;
+  } else if (max_value <= max_ticks * 4) {
+    step = 4;
+  } else if (max_value <= max_ticks * 5) {
+    step = 5;
   } else {
-    let step = 10;
+    step = 10;
     let count = 0;
     while (true) {
       if (count > 100) {
         throw Error("tick loop too long");
       }
       count += 1;
-      let top = round_up(n, step);
-      if (top <= max_ticks * step) {
-        return [step, top];
+      if (max_value <= max_ticks * step) {
+        break;
       }
       step += 10;
     }
   }
+  const tick_dist = px_per_v * step;
+  const num_ticks = Math.floor(max_value / step);
+  return {step, tick_dist, num_ticks};
 }
 
 function codegen_time(unit) {
diff --git a/src/cargo/core/compiler/timings.rs b/src/cargo/core/compiler/timings.rs
index 587bba456a5..1a3fe45052b 100644
--- a/src/cargo/core/compiler/timings.rs
+++ b/src/cargo/core/compiler/timings.rs
@@ -301,7 +301,7 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
 
     /// Save HTML report to disk.
     fn report_html(&self, bcx: &BuildContext<'_, '_>) -> CargoResult<()> {
-        let duration = self.start.elapsed().as_secs() as u32 + 1;
+        let duration = d_as_f64(self.start.elapsed());
         let timestamp = self.start_str.replace(&['-', ':'][..], "");
         let filename = format!("cargo-timing-{}.html", timestamp);
         let mut f = BufWriter::new(File::create(&filename)?);
@@ -314,11 +314,12 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
         self.write_summary_table(&mut f, duration, bcx)?;
         f.write_all(HTML_CANVAS.as_bytes())?;
         self.write_unit_table(&mut f)?;
+        // It helps with pixel alignment to use whole numbers.
         writeln!(
             f,
             "<script>\n\
              DURATION = {};",
-            duration
+            f64::ceil(duration) as u32
         )?;
         self.write_js_data(&mut f)?;
         write!(
@@ -349,7 +350,7 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
     fn write_summary_table(
         &self,
         f: &mut impl Write,
-        duration: u32,
+        duration: f64,
         bcx: &BuildContext<'_, '_>,
     ) -> CargoResult<()> {
         let targets: Vec<String> = self
@@ -358,12 +359,12 @@ impl<'a, 'cfg> Timings<'a, 'cfg> {
             .map(|(name, targets)| format!("{} ({})", name, targets.join(", ")))
             .collect();
         let targets = targets.join("<br>");
-        let time_human = if duration > 60 {
-            format!(" ({}m {:02}s)", duration / 60, duration % 60)
+        let time_human = if duration > 60.0 {
+            format!(" ({}m {:.1}s)", duration as u32 / 60, duration % 60.0)
         } else {
             "".to_string()
         };
-        let total_time = format!("{}s{}", duration, time_human);
+        let total_time = format!("{:.1}s{}", duration, time_human);
         let max_concurrency = self.concurrency.iter().map(|c| c.active).max().unwrap();
         let rustc_info = render_rustc_info(bcx);
         write!(