[gnome-disk-utility] Add back Benchmark dialog



commit e2c6aeeb79fe6e938e2b38c47ee3ac69a892950c
Author: David Zeuthen <zeuthen gmail com>
Date:   Wed Jun 13 08:30:15 2012 -0400

    Add back Benchmark dialog
    
    We lost this useful feature with the rewrite and a lot of people
    seemed to be sad about that. So here it is again, with a couple of
    improvements
    
     - Everything is done in the gnome-disks process
       (we get the fd via udisks' just-added Block.OpenForBenchmark() method)
    
     - The graph is updated while benchmark is in progress
    
     - Write-benchmarks doesn't destroy data (same data is written back)
    
     - You can benchmark volumes/partitions as well as drives
    
     - The user can configure number of samples as well as the sample size
    
    Require udisks master for this to work.
    
    Screenshots:
    
     http://people.freedesktop.org/~david/disks-benchmark-1.png
     http://people.freedesktop.org/~david/disks-benchmark-2.png
    
    Signed-off-by: David Zeuthen <zeuthen gmail com>

 configure.ac                   |    2 +-
 data/ui/Makefile.am            |    1 +
 data/ui/benchmark-dialog.ui    |  588 ++++++++++++++
 data/ui/disks.ui               |   16 +
 po/POTFILES.in                 |    2 +
 src/disks/Makefile.am          |    1 +
 src/disks/gdubenchmarkdialog.c | 1649 ++++++++++++++++++++++++++++++++++++++++
 src/disks/gdubenchmarkdialog.h |   36 +
 src/disks/gduutils.c           |   10 +-
 src/disks/gduwindow.c          |   78 ++-
 10 files changed, 2369 insertions(+), 14 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 2ae6e7f..2dfc45a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -28,7 +28,7 @@ GNOME_MAINTAINER_MODE_DEFINES
 # ***************************
 
 GLIB2_REQUIRED=2.31.0
-UDISKS2_REQUIRED=1.97.0
+UDISKS2_REQUIRED=1.99.0
 GTK3_REQUIRED=3.3.11
 KEYRING1_REQUIRED=3.4.0
 PWQUALITY_REQUIRED=1.0.0
diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am
index 08c7e48..8c5b800 100644
--- a/data/ui/Makefile.am
+++ b/data/ui/Makefile.am
@@ -22,6 +22,7 @@ ui_DATA = 				\
 	change-passphrase-dialog.ui	\
 	about-dialog.ui			\
 	app-menu.ui			\
+	benchmark-dialog.ui		\
 	$(NULL)
 
 EXTRA_DIST = 				\
diff --git a/data/ui/benchmark-dialog.ui b/data/ui/benchmark-dialog.ui
new file mode 100644
index 0000000..66c0053
--- /dev/null
+++ b/data/ui/benchmark-dialog.ui
@@ -0,0 +1,588 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkDialog" id="dialog1">
+    <property name="can_focus">False</property>
+    <property name="border_width">12</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="start-benchmark-button">
+                <property name="label" translatable="yes">_Start Benchmark...</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="stop-benchmark-button">
+                <property name="label" translatable="yes">_Abort Benchmark</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label">gtk-close</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkDrawingArea" id="graph-drawing-area">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid" id="grid2">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="row_spacing">10</property>
+                <property name="column_spacing">10</property>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Last Benchmarked</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Average Read Rate</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">2</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Average Write Rate</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">3</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Average Access Time</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">4</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="updated-label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="xalign">0</property>
+                    <property name="selectable">True</property>
+                    <property name="ellipsize">end</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="read-rate-label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="xalign">0</property>
+                    <property name="selectable">True</property>
+                    <property name="ellipsize">end</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">2</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="write-rate-label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="xalign">0</property>
+                    <property name="selectable">True</property>
+                    <property name="ellipsize">end</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">3</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="access-time-label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="xalign">0</property>
+                    <property name="selectable">True</property>
+                    <property name="ellipsize">end</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">4</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label12">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Disk Drive or Device</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="device-label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="xalign">0</property>
+                    <property name="selectable">True</property>
+                    <property name="ellipsize">end</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">start-benchmark-button</action-widget>
+      <action-widget response="1">stop-benchmark-button</action-widget>
+      <action-widget response="-7">button1</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkAdjustment" id="num-access-samples-adjustment">
+    <property name="lower">2</property>
+    <property name="upper">10000</property>
+    <property name="value">1000</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="num-samples-adjustment">
+    <property name="lower">2</property>
+    <property name="upper">1000</property>
+    <property name="value">100</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="sample-size-adjustment">
+    <property name="lower">1</property>
+    <property name="upper">1000</property>
+    <property name="value">10</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkDialog" id="dialog2">
+    <property name="can_focus">False</property>
+    <property name="border_width">12</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox2">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area2">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button2">
+                <property name="label">gtk-cancel</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button3">
+                <property name="label" translatable="yes">_Start Benchmarking...</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkLabel" id="label8">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Benchmarking involves measuring the transfer rate on various area of the device as well as measuring how long it takes to seek from one random area to another. Please back up important data before using the write benchmark.</property>
+                <property name="use_markup">True</property>
+                <property name="wrap">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label9">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;b&gt;Transfer Rate&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid" id="grid1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">24</property>
+                <property name="row_spacing">10</property>
+                <property name="column_spacing">10</property>
+                <child>
+                  <object class="GtkLabel" id="label5">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Number of S_amples</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">num-samples-spinbutton</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label6">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Sample S_ize (MiB)</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">sample-size-spinbutton</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="write-checkbutton">
+                    <property name="label" translatable="yes">Also perform _write-benchmark</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="tooltip_text" translatable="yes">Bechmarking the write-rate of a disk requires exclusive access to the disk (e.g. the disk or its partitions cannot be mounted or in use) and involves reading data and then writing it back. As a result, the contents of the disk is not changed.
+
+If not checked, the write-part of the benchmark will not be done but on the other hand exclusive access to the device is not needed (e.g. the disk or device can be in use) .</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="xalign">0</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">2</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSpinButton" id="num-samples-spinbutton">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="tooltip_text" translatable="yes">Number of samples. Bigger number produces a more accurate picture of access time patterns but takes more time.</property>
+                    <property name="hexpand">True</property>
+                    <property name="invisible_char">â</property>
+                    <property name="adjustment">num-samples-adjustment</property>
+                    <property name="numeric">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSpinButton" id="sample-size-spinbutton">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="tooltip_text" translatable="yes">The number of MiB (1048576 bytes) to read/write for each sample. Big sample sizes tend to produce more accurate benchmarks at the cost of the benchmark taking more time.</property>
+                    <property name="hexpand">True</property>
+                    <property name="invisible_char">â</property>
+                    <property name="adjustment">sample-size-adjustment</property>
+                    <property name="numeric">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label7">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">2</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label10">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;b&gt;Access Time&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid" id="grid3">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">24</property>
+                <property name="row_spacing">10</property>
+                <property name="column_spacing">10</property>
+                <child>
+                  <object class="GtkLabel" id="label11">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">Number of Sampl_es</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">num-access-samples-spinbutton</property>
+                    <style><class name="dim-label"/></style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSpinButton" id="num-access-samples-spinbutton">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="has_tooltip">True</property>
+                    <property name="tooltip_markup" translatable="yes">Number of samples. Bigger number produces more smooth graphs but the benchmark will take more time.</property>
+                    <property name="tooltip_text" translatable="yes">Number of samples. Bigger number produces more smooth graphs but the benchmark will take more time.</property>
+                    <property name="hexpand">True</property>
+                    <property name="invisible_char">â</property>
+                    <property name="invisible_char_set">True</property>
+                    <property name="adjustment">num-access-samples-adjustment</property>
+                    <property name="numeric">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">4</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">button2</action-widget>
+      <action-widget response="-5">button3</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/data/ui/disks.ui b/data/ui/disks.ui
index f16bd71..065fd1c 100644
--- a/data/ui/disks.ui
+++ b/data/ui/disks.ui
@@ -97,6 +97,14 @@
       </object>
     </child>
     <child>
+      <object class="GtkMenuItem" id="generic-drive-menu-item-benchmark">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="use_action_appearance">False</property>
+        <property name="label" translatable="yes">Benchmark Drive...</property>
+      </object>
+    </child>
+    <child>
       <object class="GtkMenuItem" id="generic-drive-menu-item-view-smart">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
@@ -201,6 +209,14 @@
         <property name="use_underline">True</property>
       </object>
     </child>
+    <child>
+      <object class="GtkMenuItem" id="generic-menu-item-benchmark">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="use_action_appearance">False</property>
+        <property name="label" translatable="yes">Benchmark Volume...</property>
+      </object>
+    </child>
   </object>
   <object class="GtkWindow" id="disks-window">
     <property name="can_focus">False</property>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0578761..419ab7c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -5,6 +5,7 @@ data/gnome-disk-image-mounter.desktop.in
 data/gnome-disks.desktop.in
 [type: gettext/glade]data/ui/about-dialog.ui
 [type: gettext/glade]data/ui/app-menu.ui
+[type: gettext/glade]data/ui/benchmark-dialog.ui
 [type: gettext/glade]data/ui/change-passphrase-dialog.ui
 [type: gettext/glade]data/ui/create-disk-image-dialog.ui
 [type: gettext/glade]data/ui/create-partition-dialog.ui
@@ -24,6 +25,7 @@ data/gnome-disks.desktop.in
 src/disk-image-mounter/main.c
 src/disks/gduapplication.c
 src/disks/gduatasmartdialog.c
+src/disks/gdubenchmarkdialog.c
 src/disks/gduchangepassphrasedialog.c
 src/disks/gducreatediskimagedialog.c
 src/disks/gducreatefilesystemwidget.c
diff --git a/src/disks/Makefile.am b/src/disks/Makefile.am
index cbac492..5d1ac5d 100644
--- a/src/disks/Makefile.am
+++ b/src/disks/Makefile.am
@@ -33,6 +33,7 @@ gnome_disks_SOURCES = 							\
 	gduvolumegrid.h			gduvolumegrid.c			\
 	gduwindow.h			gduwindow.c			\
 	gduatasmartdialog.h		gduatasmartdialog.c		\
+	gdubenchmarkdialog.h		gdubenchmarkdialog.c		\
 	gducrypttabdialog.h		gducrypttabdialog.c		\
 	gdufilesystemdialog.h		gdufilesystemdialog.c		\
 	gdufstabdialog.h		gdufstabdialog.c		\
diff --git a/src/disks/gdubenchmarkdialog.c b/src/disks/gdubenchmarkdialog.c
new file mode 100644
index 0000000..e801fc5
--- /dev/null
+++ b/src/disks/gdubenchmarkdialog.c
@@ -0,0 +1,1649 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gio/gunixfdlist.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+
+#include <glib-unix.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+
+#include <math.h>
+
+#include "gduapplication.h"
+#include "gduwindow.h"
+#include "gdubenchmarkdialog.h"
+#include "gduutils.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+  guint64 offset;
+  gdouble value;
+} BMSample;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum {
+  BM_STATE_NONE,
+  BM_STATE_OPENING_DEVICE,
+  BM_STATE_TRANSFER_RATE,
+  BM_STATE_ACCESS_TIME,
+} BMState;
+
+typedef struct
+{
+  volatile gint ref_count;
+
+  UDisksObject *object;
+  UDisksBlock *block;
+
+  GCancellable *cancellable;
+
+  GduWindow *window;
+  GtkBuilder *builder;
+
+  GtkWidget *dialog;
+
+  GtkWidget *graph_drawing_area;
+
+  GtkWidget *device_label;
+  GtkWidget *updated_label;
+  GtkWidget *read_rate_label;
+  GtkWidget *write_rate_label;
+  GtkWidget *access_time_label;
+
+  GtkWidget *start_benchmark_button;
+  GtkWidget *stop_benchmark_button;
+
+  gboolean closed;
+
+  /* ---- */
+
+  /* retrieved from preferences dialog */
+  gint bm_num_samples;
+  gint bm_sample_size_mib;
+  gboolean bm_do_write;
+  gint bm_num_access_samples;
+
+  /* must hold bm_lock when reading/writing these */
+  GThread *bm_thread;
+  GCancellable *bm_cancellable;
+  gboolean bm_in_progress;
+  BMState bm_state;
+  GError *bm_error; /* set by benchmark thread on termination */
+  gboolean bm_update_timeout_pending;
+
+  gint64 bm_time_benchmarked_usec; /* 0 if never benchmarked, otherwise micro-seconds since Epoch */
+  guint64 bm_size;
+  GArray *bm_read_samples;
+  GArray *bm_write_samples;
+  GArray *bm_access_time_samples;
+
+} DialogData;
+
+G_LOCK_DEFINE (bm_lock);
+
+static const struct {
+  goffset offset;
+  const gchar *name;
+} widget_mapping[] = {
+  {G_STRUCT_OFFSET (DialogData, graph_drawing_area), "graph-drawing-area"},
+  {G_STRUCT_OFFSET (DialogData, device_label), "device-label"},
+  {G_STRUCT_OFFSET (DialogData, updated_label), "updated-label"},
+  {G_STRUCT_OFFSET (DialogData, read_rate_label), "read-rate-label"},
+  {G_STRUCT_OFFSET (DialogData, write_rate_label), "write-rate-label"},
+  {G_STRUCT_OFFSET (DialogData, access_time_label), "access-time-label"},
+  {0, NULL}
+};
+
+static void update_dialog (DialogData *data);
+
+static gboolean maybe_load_data (DialogData  *data,
+                                 GError     **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static DialogData *
+dialog_data_ref (DialogData *data)
+{
+  g_atomic_int_inc (&data->ref_count);
+  return data;
+}
+
+static void
+dialog_data_unref (DialogData *data)
+{
+  if (g_atomic_int_dec_and_test (&data->ref_count))
+    {
+      if (data->dialog != NULL)
+        {
+          gtk_widget_hide (data->dialog);
+          gtk_widget_destroy (data->dialog);
+          data->dialog = NULL;
+        }
+
+      g_clear_object (&data->object);
+      g_clear_object (&data->window);
+      g_clear_object (&data->builder);
+
+      g_array_unref (data->bm_read_samples);
+      g_array_unref (data->bm_write_samples);
+      g_array_unref (data->bm_access_time_samples);
+      g_clear_object (&data->bm_cancellable);
+      g_clear_error (&data->bm_error);
+
+      g_free (data);
+    }
+}
+
+static void
+dialog_data_close (DialogData *data)
+{
+  g_cancellable_cancel (data->bm_cancellable);
+  data->closed = TRUE;
+  gtk_dialog_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_CANCEL);
+  dialog_data_unref (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_max_min_avg (GArray  *array,
+                 gdouble *out_max,
+                 gdouble *out_min,
+                 gdouble *out_avg)
+{
+  guint n;
+  gdouble max = 0;
+  gdouble min = 0;
+  gdouble avg = 0;
+  gdouble sum = 0;
+
+  if (array->len == 0)
+    goto out;
+
+  max = -G_MAXDOUBLE;
+  min = G_MAXDOUBLE;
+  sum = 0;
+
+  for (n = 0; n < array->len; n++)
+    {
+      BMSample *s = &g_array_index (array, BMSample, n);
+      if (s->value > max)
+        max = s->value;
+      if (s->value < min)
+        min = s->value;
+      sum += s->value;
+    }
+  avg = sum / array->len;
+
+ out:
+  if (out_max != NULL)
+    *out_max = max;
+  if (out_min != NULL)
+    *out_min = min;
+  if (out_avg != NULL)
+    *out_avg = avg;
+}
+
+static gdouble
+measure_width (cairo_t     *cr,
+               const gchar *s)
+{
+  cairo_text_extents_t te;
+  cairo_text_extents (cr, s, &te);
+  return te.width;
+}
+
+static gdouble
+measure_height (cairo_t     *cr,
+                const gchar *s)
+{
+  cairo_text_extents_t te;
+  cairo_text_extents (cr, s, &te);
+  return te.height;
+}
+
+static gboolean
+on_drawing_area_draw (GtkWidget      *widget,
+                      cairo_t        *cr,
+                      gpointer        user_data)
+{
+  DialogData *data = user_data;
+  GtkAllocation allocation;
+  gdouble width, height;
+  gdouble gx, gy, gw, gh;
+  guint n;
+  gdouble w, h;
+  gdouble x, y;
+  gdouble x_marker_height;
+  gchar *s;
+  gdouble max_speed;
+  gdouble max_visible_speed;
+  gdouble speed_res;
+  gdouble max_time;
+  gdouble time_res;
+  gdouble max_visible_time;
+  gchar **y_left_markers;
+  gchar **y_right_markers;
+  guint num_y_markers;
+  GPtrArray *p;
+  GPtrArray *p2;
+  gdouble read_transfer_rate_max = 0.0;
+  gdouble write_transfer_rate_max = 0.0;
+  gdouble access_time_max = 0.0;
+  gdouble prev_x;
+  gdouble prev_y;
+
+  G_LOCK (bm_lock);
+
+  //g_print ("drawing: %d %d %d\n",
+  //         data->bm_read_samples->len,
+  //         data->bm_write_samples->len,
+  //         data->bm_access_time_samples->len);
+
+  get_max_min_avg (data->bm_read_samples,
+                   &read_transfer_rate_max,
+                   NULL,
+                   NULL);
+  get_max_min_avg (data->bm_write_samples,
+                   &write_transfer_rate_max,
+                   NULL,
+                   NULL);
+  get_max_min_avg (data->bm_access_time_samples,
+                   &access_time_max,
+                   NULL,
+                   NULL);
+
+  max_speed = MAX (read_transfer_rate_max, write_transfer_rate_max);
+  max_time = access_time_max;
+
+  if (max_speed == 0)
+    max_speed = 100 * 1000 * 1000;
+
+  if (max_time == 0)
+    max_time = 50 / 1000.0;
+
+  //speed_res = (floor (((gdouble) max_speed) / (100 * 1000 * 1000)) + 1) * 1000 * 1000;
+  //speed_res *= 10.0;
+
+  speed_res = max_speed / 10.0;
+  /* round up to nearest multiple of 10 MB/s */
+  max_visible_speed = ceil (max_speed / (10*1000*1000)) * 10*1000*1000;
+  speed_res = max_visible_speed / 10.0;
+  num_y_markers = 10;
+
+  time_res = max_time / num_y_markers;
+  if (time_res < 0.0001)
+    {
+      time_res = 0.0001;
+    }
+  else if (time_res < 0.0005)
+    {
+      time_res = 0.0005;
+    }
+  else if (time_res < 0.001)
+    {
+      time_res = 0.001;
+    }
+  else if (time_res < 0.0025)
+    {
+      time_res = 0.0025;
+    }
+  else if (time_res < 0.005)
+    {
+      time_res = 0.005;
+    }
+  else
+    {
+      time_res = ceil (((gdouble) time_res) / 0.005) * 0.005;
+    }
+  max_visible_time = time_res * num_y_markers;
+
+  //g_print ("max_visible_speed=%f, max_speed=%f, speed_res=%f\n", max_visible_speed, max_speed, speed_res);
+  //g_print ("max_visible_time=%f, max_time=%f, time_res=%f\n", max_visible_time, max_time, time_res);
+
+  p = g_ptr_array_new ();
+  p2 = g_ptr_array_new ();
+  for (n = 0; n <= num_y_markers; n++)
+    {
+      gdouble val;
+
+      val = n * speed_res;
+      /* Translators: This is used in the benchmark graph - %d is megabytes per second */
+      s = g_strdup_printf (C_("benchmark-graph", "%d MB/s"), (gint) (val / (1000 * 1000)));
+      g_ptr_array_add (p, s);
+
+      val = n * time_res;
+      /* Translators: This is used in the benchmark graph - %g is number of milliseconds */
+      s = g_strdup_printf (C_("benchmark-graph", "%3g ms"), val * 1000.0);
+      g_ptr_array_add (p2, s);
+    }
+  g_ptr_array_add (p, NULL);
+  g_ptr_array_add (p2, NULL);
+  y_left_markers = (gchar **) g_ptr_array_free (p, FALSE);
+  y_right_markers = (gchar **) g_ptr_array_free (p2, FALSE);
+
+  gtk_widget_get_allocation (widget, &allocation);
+  width = allocation.width;
+  height = allocation.height;
+
+  cairo_select_font_face (cr, "sans",
+                          CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+  cairo_set_font_size (cr, 8.0);
+  cairo_set_line_width (cr, 1.0);
+
+#if 0
+  cairo_set_source_rgb (cr, 0.25, 0.25, 0.25);
+  cairo_rectangle (cr, 0, 0, width, height);
+  cairo_set_line_width (cr, 0.0);
+  cairo_fill (cr);
+#endif
+
+  gx = 0;
+  gy = 0;
+  gw = width;
+  gh = height;
+
+  /* make horizontal and vertical room for x markers ("%d%%") */
+  w = ceil (measure_width (cr, "0%") / 2.0);
+  gx +=  w;
+  gw -=  w;
+  w = ceil (measure_width (cr, "100%") / 2.0);
+  x_marker_height = ceil (measure_height (cr, "100%")) + 10;
+  gw -= w;
+  gh -= x_marker_height;
+
+  /* make horizontal room for left y markers ("%d MB/s") */
+  for (n = 0; n <= num_y_markers; n++)
+    {
+      w = ceil (measure_width (cr, y_left_markers[n])) + 2 * 3;
+      if (w > gx)
+        {
+          gdouble needed = w - gx;
+          gx += needed;
+          gw -= needed;
+        }
+    }
+
+  /* make vertical room for top-left y marker */
+  h = ceil (measure_height (cr, y_left_markers[num_y_markers]) / 2.0);
+  if (h > gy)
+    {
+      gdouble needed = h - gy;
+      gy += needed;
+      gh -= needed;
+    }
+
+  /* make horizontal room for right y markers ("%d ms") */
+  for (n = 0; n <= num_y_markers; n++)
+    {
+      w = ceil (measure_width (cr, y_right_markers[n])) + 2 * 3;
+      if (w > width - (gx + gw))
+        {
+          gdouble needed = w - (width - (gx + gw));
+          gw -= needed;
+        }
+    }
+
+  /* make vertical room for top-right y marker */
+  h = ceil (measure_height (cr, y_right_markers[num_y_markers]) / 2.0);
+  if (h > gy)
+    {
+      gdouble needed = h - gy;
+      gy += needed;
+      gh -= needed;
+    }
+
+  /* draw x markers ("%d%%") + vertical grid */
+  for (n = 0; n <= 10; n++)
+    {
+      cairo_text_extents_t te;
+
+      x = gx + ceil (n * gw / 10.0);
+      y = gy + gh + x_marker_height/2.0;
+
+      s = g_strdup_printf ("%d%%", n * 10);
+
+      cairo_text_extents (cr, s, &te);
+
+      cairo_move_to (cr,
+                     x - te.x_bearing - te.width/2,
+                     y - te.y_bearing - te.height/2);
+      cairo_set_source_rgb (cr, 0, 0, 0);
+      cairo_show_text (cr, s);
+
+      g_free (s);
+    }
+
+  /* draw left y markers ("%d MB/s") */
+  for (n = 0; n <= num_y_markers; n++)
+    {
+      cairo_text_extents_t te;
+
+      x = gx/2.0;
+      y = gy + gh - gh * n / num_y_markers;
+
+      s = y_left_markers[n];
+      cairo_text_extents (cr, s, &te);
+      cairo_move_to (cr,
+                     x - te.x_bearing - te.width/2,
+                     y - te.y_bearing - te.height/2);
+      cairo_set_source_rgb (cr, 0, 0, 0);
+      cairo_show_text (cr, s);
+    }
+
+  /* draw right y markers ("%d ms") */
+  for (n = 0; n <= num_y_markers; n++)
+    {
+      cairo_text_extents_t te;
+
+      x = gx + gw + (width - (gx + gw))/2.0;
+      y = gy + gh - gh * n / num_y_markers;
+
+      s = y_right_markers[n];
+      cairo_text_extents (cr, s, &te);
+      cairo_move_to (cr,
+                     x - te.x_bearing - te.width/2,
+                     y - te.y_bearing - te.height/2);
+      cairo_set_source_rgb (cr, 0, 0, 0);
+      cairo_show_text (cr, s);
+    }
+
+  /* fill graph area */
+  cairo_set_source_rgb (cr, 1, 1, 1);
+  cairo_rectangle (cr, gx + 0.5, gy + 0.5, gw, gh);
+  cairo_fill_preserve (cr);
+  /* grid - first a rect */
+  cairo_set_source_rgba (cr, 0, 0, 0, 0.25);
+  cairo_set_line_width (cr, 1.0);
+  /* rect - also clip to rect for all future drawing operations */
+  cairo_stroke_preserve (cr);
+  cairo_clip (cr);
+  /* vertical lines */
+  for (n = 1; n < 10; n++)
+    {
+      x = gx + ceil (n * gw / 10.0);
+      cairo_move_to (cr, x + 0.5, gy + 0.5);
+      cairo_line_to (cr, x + 0.5, gy + gh + 0.5);
+      cairo_stroke (cr);
+    }
+  /* horizontal lines */
+  for (n = 1; n < num_y_markers; n++)
+    {
+      y = gy + ceil (n * gh / num_y_markers);
+      cairo_move_to (cr, gx + 0.5, y + 0.5);
+      cairo_line_to (cr, gx + gw + 0.5, y + 0.5);
+      cairo_stroke (cr);
+    }
+
+  /* draw read graph */
+  cairo_set_source_rgb (cr, 0.5, 0.5, 1.0);
+  cairo_set_line_width (cr, 1.5);
+  for (n = 0; n < data->bm_read_samples->len; n++)
+    {
+      BMSample *s = &g_array_index (data->bm_read_samples, BMSample, n);
+
+      x = gx + gw * s->offset / data->bm_size;
+      y = gy + gh - gh * s->value / max_visible_speed;
+
+      if (n == 0)
+        cairo_move_to (cr, x, y);
+      else
+        cairo_line_to (cr, x, y);
+    }
+  cairo_stroke (cr);
+
+  /* draw write graph */
+  cairo_set_source_rgb (cr, 1.0, 0.5, 0.5);
+  cairo_set_line_width (cr, 1.5);
+  for (n = 0; n < data->bm_write_samples->len; n++)
+    {
+      BMSample *s = &g_array_index (data->bm_write_samples, BMSample, n);
+      x = gx + gw * s->offset / data->bm_size;
+      y = gy + gh - gh * s->value / max_visible_speed;
+
+      if (n == 0)
+        cairo_move_to (cr, x, y);
+      else
+        cairo_line_to (cr, x, y);
+    }
+  cairo_stroke (cr);
+
+  /* draw access time dots + lines */
+  cairo_set_line_width (cr, 0.5);
+  for (n = 0; n < data->bm_access_time_samples->len; n++)
+    {
+      BMSample *s = &g_array_index (data->bm_access_time_samples, BMSample, n);
+
+      x = gx + gw * s->offset / data->bm_size;
+      y = gy + gh - gh * s->value / max_visible_time;
+
+      /*g_debug ("time = %f @ %f", point->value, x);*/
+
+      cairo_set_source_rgba (cr, 0.4, 1.0, 0.4, 0.5);
+      cairo_arc (cr, x, y, 1.5, 0, 2 * M_PI);
+      cairo_fill (cr);
+
+      if (n > 0)
+        {
+          cairo_set_source_rgba (cr, 0.2, 0.5, 0.2, 0.10);
+          cairo_move_to (cr, prev_x, prev_y);
+          cairo_line_to (cr, x, y);
+          cairo_stroke (cr);
+        }
+
+      prev_x = x;
+      prev_y = y;
+    }
+
+#if 0
+  if (dialog->priv->benchmark_data != NULL) {
+                BenchmarkData *data = dialog->priv->benchmark_data;
+                gdouble prev_x;
+                gdouble prev_y;
+
+                /* draw access time dots + lines */
+                cairo_set_line_width (cr, 0.5);
+                for (n = 0; n < data->access_time_samples->len; n++) {
+                        BenchmarkPoint *point = &g_array_index (data->access_time_samples, BenchmarkPoint, n);
+
+                        x = gx + gw * point->offset / data->disk_size;
+                        y = gy + gh - gh * point->value / max_visible_time;
+
+                        /*g_debug ("time = %f @ %f", point->value, x);*/
+
+                        cairo_set_source_rgba (cr, 0.4, 1.0, 0.4, 0.5);
+                        cairo_arc (cr, x, y, 1.5, 0, 2 * M_PI);
+                        cairo_fill (cr);
+
+                        if (n > 0) {
+                                cairo_set_source_rgba (cr, 0.2, 0.5, 0.2, 0.10);
+                                cairo_move_to (cr, prev_x, prev_y);
+                                cairo_line_to (cr, x, y);
+                                cairo_stroke (cr);
+                        }
+
+                        prev_x = x;
+                        prev_y = y;
+                }
+
+                /* draw write transfer rate graph */
+                cairo_set_source_rgb (cr, 1.0, 0.5, 0.5);
+                cairo_set_line_width (cr, 2.0);
+                for (n = 0; n < data->write_transfer_rate_samples->len; n++) {
+                        BenchmarkPoint *point = &g_array_index (data->write_transfer_rate_samples, BenchmarkPoint, n);
+
+                        x = gx + gw * point->offset / data->disk_size;
+                        y = gy + gh - gh * point->value / max_visible_speed;
+
+                        if (n == 0)
+                                cairo_move_to (cr, x, y);
+                        else
+                                cairo_line_to (cr, x, y);
+                }
+                cairo_stroke (cr);
+
+                /* draw read transfer rate graph */
+                cairo_set_source_rgb (cr, 0.5, 0.5, 1.0);
+                cairo_set_line_width (cr, 1.5);
+                for (n = 0; n < data->read_transfer_rate_samples->len; n++) {
+                        BenchmarkPoint *point = &g_array_index (data->read_transfer_rate_samples, BenchmarkPoint, n);
+
+                        x = gx + gw * point->offset / data->disk_size;
+                        y = gy + gh - gh * point->value / max_visible_speed;
+
+                        if (n == 0)
+                                cairo_move_to (cr, x, y);
+                        else
+                                cairo_line_to (cr, x, y);
+                }
+                cairo_stroke (cr);
+
+        } else {
+                /* TODO: draw some text saying we don't have any data */
+        }
+#endif
+
+        g_strfreev (y_left_markers);
+        g_strfreev (y_right_markers);
+
+  G_UNLOCK (bm_lock);
+
+  /* propagate event further */
+  return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+format_transfer_rate (gdouble bytes_per_sec)
+{
+  gchar *ret = NULL;
+  gchar *s;
+
+  s = g_format_size ((guint64) bytes_per_sec);
+  /* Translators: %s is the formatted size, e.g. "42 MB" and the trailing "/s" means per second */
+  ret = g_strdup_printf (C_("benchmark-transfer-rate", "%s/s"), s);
+  g_free (s);
+  return ret;
+}
+
+static void
+update_updated_label (DialogData *data)
+{
+  gchar *s = NULL;
+
+  G_LOCK (bm_lock);
+  switch (data->bm_state)
+    {
+    case BM_STATE_NONE:
+      if (data->bm_time_benchmarked_usec > 0)
+        {
+          gint64 now_usec;
+          gchar *s2;
+          GDateTime *time_benchmarked_dt;
+          GDateTime *time_benchmarked_dt_local;
+          gchar *time_benchmarked_str;
+
+          now_usec = g_get_real_time ();
+
+          time_benchmarked_dt = g_date_time_new_from_unix_utc (data->bm_time_benchmarked_usec / G_USEC_PER_SEC);
+          time_benchmarked_dt_local = g_date_time_to_local (time_benchmarked_dt);
+          time_benchmarked_str = g_date_time_format (time_benchmarked_dt_local, "%c");
+
+          s = gdu_utils_duration_to_string ((now_usec - data->bm_time_benchmarked_usec) / G_USEC_PER_SEC, FALSE);
+          /* Translators: The first %s is the date and time the benchmark took place in the preferred
+           * format for the locale (e.g. "%c" for strftime()/g_date_time_format()), for example
+           * "Tue 12 Jun 2012 03:57:08 PM EDT". The second %s is how long ago that is from right
+           * now, for example "3 days" or "2 hours" or "12 minutes".
+           */
+          s2 = g_strdup_printf (C_("benchmark-updated", "%s (%s ago)"),
+                                time_benchmarked_str,
+                                s);
+          gtk_label_set_text (GTK_LABEL (data->updated_label), s2);
+          g_free (s2);
+          g_free (s);
+          g_free (time_benchmarked_str);
+          g_date_time_unref (time_benchmarked_dt_local);
+          g_date_time_unref (time_benchmarked_dt);
+        }
+      else
+        {
+          gtk_label_set_markup (GTK_LABEL (data->updated_label), C_("benchmark-updated", "No benchmark data available"));
+        }
+      break;
+
+    case BM_STATE_OPENING_DEVICE:
+      gtk_label_set_markup (GTK_LABEL (data->updated_label), C_("benchmark-updated", "Opening Device..."));
+      break;
+
+    case BM_STATE_TRANSFER_RATE:
+      s = g_strdup_printf (C_("benchmark-updated", "Measuring transfer rate (%2.1f%% complete)..."),
+                           data->bm_read_samples->len * 100.0 / data->bm_num_samples);
+      gtk_label_set_markup (GTK_LABEL (data->updated_label), s);
+      g_free (s);
+      break;
+
+    case BM_STATE_ACCESS_TIME:
+      s = g_strdup_printf (C_("benchmark-updated", "Measuring access time (%2.1f%% complete)..."),
+                           data->bm_access_time_samples->len * 100.0 / data->bm_num_access_samples);
+      gtk_label_set_markup (GTK_LABEL (data->updated_label), s);
+      g_free (s);
+      break;
+    }
+  G_UNLOCK (bm_lock);
+}
+
+/* returns NULL if it doesn't make sense to load/save benchmark data (removable media,
+ * non-drive devices etc.)
+ */
+static gchar *
+get_bm_filename (DialogData *data)
+{
+  gchar *ret = NULL;
+  UDisksDrive *drive = NULL;
+  GDBusObject *drive_object = NULL;
+  UDisksPartition *partition = NULL;
+  const gchar *object_path;
+  gchar *bench_dir = NULL;
+  gchar *tmp;
+  guint n;
+
+  /* If the device has a _distinct_ preferred name, use that for the filename */
+  if (g_strcmp0 (udisks_block_get_preferred_device (data->block),
+                 udisks_block_get_device (data->block)) != 0)
+    {
+      ret = g_strdup (udisks_block_get_preferred_device (data->block));
+      for (n = 0; ret[n] != '\0'; n++)
+        {
+          if (ret[n] == '/')
+            {
+              ret[n] = '_';
+            }
+        }
+    }
+  else
+    {
+      /* otherwise, we only load/save benchmarks for drives... */
+      drive = udisks_client_get_drive_for_block (gdu_window_get_client (data->window), data->block);
+      if (drive == NULL)
+        goto out;
+
+      /* ... where the medium is not removable (the benchmark would be for the media, not the drive) */
+      if (udisks_drive_get_media_removable (drive))
+        goto out;
+
+      drive_object = g_dbus_interface_dup_object (G_DBUS_INTERFACE (drive));
+      if (drive_object == NULL)
+        goto out;
+
+      object_path = g_dbus_object_get_object_path (drive_object);
+      object_path = strrchr (object_path, '/');
+      if (object_path == NULL)
+        goto out;
+
+      ret = g_strdup (object_path + 1);
+
+      partition = udisks_object_get_partition (data->object);
+      if (partition != NULL)
+        {
+          tmp = ret;
+          ret = g_strdup_printf ("%s-part-offset%lld-size%lld", ret,
+                                 (long long int) udisks_partition_get_offset (partition),
+                                 (long long int) udisks_partition_get_size (partition));
+          g_free (tmp);
+        }
+    }
+
+  bench_dir = g_strdup_printf ("%s/gnome-disks/benchmarks",
+                               g_get_user_cache_dir ());
+  if (g_mkdir_with_parents (bench_dir, 0777) != 0)
+    {
+      g_warning ("Error creating directory %s: %m", bench_dir);
+      goto out;
+    }
+
+  tmp = ret;
+  ret = g_strdup_printf ("%s/%s.gnome-disks-benchmark", bench_dir, ret);
+  g_free (tmp);
+
+ out:
+  g_free (bench_dir);
+  g_clear_object (&drive_object);
+  g_clear_object (&drive);
+  g_clear_object (&partition);
+  return ret;
+}
+
+static void
+update_dialog (DialogData *data)
+{
+  GError *error = NULL;
+  GdkWindow *window = NULL;
+  gdouble read_avg = 0.0;
+  gdouble write_avg = 0.0;
+  gdouble access_time_avg = 0.0;
+  gchar *s = NULL;
+  UDisksDrive *drive = NULL;
+  UDisksPartition *partition = NULL;
+
+  G_LOCK (bm_lock);
+  if (data->bm_error != NULL)
+    {
+      error = data->bm_error;
+      data->bm_error = NULL;
+    }
+  G_UNLOCK (bm_lock);
+
+  /* first of all, present an error if something went wrong */
+  if (error != NULL)
+    {
+      if (!data->closed)
+        {
+          if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
+            gdu_window_show_error (data->window, C_("benchmarking", "An error occurred"), error);
+        }
+      g_clear_error (&error);
+
+      /* and reload old data */
+      if (!maybe_load_data (data, &error))
+        {
+          /* not worth complaining in dialog about */
+          g_warning ("Error loading cached data: %s (%s, %d)",
+                     error->message, g_quark_to_string (error->domain), error->code);
+          g_clear_error (&error);
+        }
+    }
+
+  update_updated_label (data);
+
+  /* disk / device label */
+  drive = udisks_client_get_drive_for_block (gdu_window_get_client (data->window), data->block);
+  partition = udisks_object_get_partition (data->object);
+  if (drive != NULL)
+    {
+      gchar *name;
+      gchar *desc;
+      udisks_client_get_drive_info (gdu_window_get_client (data->window),
+                                    drive,
+                                    &name,
+                                    &desc,
+                                    NULL,   /* out_drive_icon */
+                                    NULL,   /* out_media_desc */
+                                    NULL);  /* out_media_icon */
+      if (partition != NULL)
+        {
+          gchar *str_size = g_format_size (udisks_block_get_size (data->block));
+          /* Translators: The first %s is the partition size (e.g. "10.0 GB") and the two
+           * following %s are make/model (e.g. "ST 3160A") and description (e.g. "60 GB Hard Disk")
+           */
+          s = g_strdup_printf (C_("benchmark-drive-name", "%s partition on %s (%s)"),
+                               str_size,
+                               name, desc);
+          g_free (str_size);
+        }
+      else
+        {
+          /* Translators: The first %s is the make/model (e.g. "ST 3160A"), the second %s is
+           * the description (e.g. "60 GB Hard Disk")
+           */
+          s = g_strdup_printf (C_("benchmark-drive-name", "%s (%s)"), name, desc);
+        }
+      g_free (name);
+      g_free (desc);
+    }
+  else
+    {
+      s = udisks_block_dup_preferred_device (data->block);
+    }
+  gtk_label_set_text (GTK_LABEL (data->device_label), s);
+  g_free (s);
+
+
+  G_LOCK (bm_lock);
+
+  if (data->bm_in_progress)
+    {
+
+      gtk_widget_hide (data->start_benchmark_button);
+      gtk_widget_show (data->stop_benchmark_button);
+    }
+  else
+    {
+      gtk_widget_show (data->start_benchmark_button);
+      gtk_widget_hide (data->stop_benchmark_button);
+    }
+
+  get_max_min_avg (data->bm_read_samples,
+                   NULL, NULL, &read_avg);
+  get_max_min_avg (data->bm_write_samples,
+                   NULL, NULL, &write_avg);
+  get_max_min_avg (data->bm_access_time_samples,
+                   NULL, NULL, &access_time_avg);
+
+  G_UNLOCK (bm_lock);
+
+  if (read_avg == 0.0)
+    s = g_strdup ("â");
+  else
+    s = format_transfer_rate (read_avg);
+  gtk_label_set_markup (GTK_LABEL (data->read_rate_label), s);
+  g_free (s);
+
+  if (write_avg == 0.0)
+    s = g_strdup ("â");
+  else
+    s = format_transfer_rate (write_avg);
+  gtk_label_set_markup (GTK_LABEL (data->write_rate_label), s);
+  g_free (s);
+
+  if (access_time_avg == 0.0)
+    {
+      s = g_strdup ("â");
+    }
+  else
+    {
+      /* Translators: %d is number of milliseconds and msec means "milli-second" */
+      s = g_strdup_printf (C_("benchmark-access-time", "%.2f msec"), access_time_avg * 1000.0);
+    }
+  gtk_label_set_markup (GTK_LABEL (data->access_time_label), s);
+  g_free (s);
+
+
+  window = gtk_widget_get_window (data->graph_drawing_area);
+  if (window != NULL)
+    gdk_window_invalidate_rect (window, NULL, TRUE);
+
+  g_clear_object (&drive);
+  g_clear_object (&partition);
+}
+
+
+/* called every second (on the second) */
+static gboolean
+on_timeout (gpointer user_data)
+{
+  DialogData *data = user_data;
+  update_updated_label (data);
+  return TRUE; /* keep timeout around */
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+samples_from_gvariant (GArray   *array,
+                       GVariant *variant)
+{
+  GVariantIter iter;
+  BMSample sample;
+
+  g_array_set_size (array, 0);
+
+  g_variant_iter_init (&iter, variant);
+  while (g_variant_iter_next (&iter, "(td)", &sample.offset, &sample.value))
+    {
+      g_array_append_val (array, sample);
+    }
+}
+
+static gboolean
+maybe_load_data (DialogData  *data,
+                 GError     **error)
+{
+  gboolean ret = FALSE;
+  gchar *filename = NULL;
+  GVariant *value = NULL;
+  gchar *variant_data = NULL;
+  gsize variant_size;
+  GError *local_error = NULL;
+  GVariant *read_samples_variant = NULL;
+  GVariant *write_samples_variant = NULL;
+  GVariant *access_time_samples_variant = NULL;
+  gint32 version;
+  gint64 timestamp_usec;
+  guint64 device_size;
+
+  filename = get_bm_filename (data);
+  if (filename == NULL)
+    {
+      /* all good since we don't want to load data for this device */
+      ret = TRUE;
+      goto out;
+    }
+
+  if (!g_file_get_contents (filename,
+                            &variant_data,
+                            &variant_size,
+                            &local_error))
+    {
+      if (local_error->domain == G_FILE_ERROR && local_error->code == G_FILE_ERROR_NOENT)
+        {
+          /* don't complain about a missing file */
+          g_clear_error (&local_error);
+          ret = TRUE;
+          goto out;
+        }
+      g_propagate_error (error, local_error);
+      goto out;
+    }
+
+  value = g_variant_new_from_data (G_VARIANT_TYPE_VARDICT,
+                                   variant_data,
+                                   variant_size,
+                                   FALSE,
+                                   NULL, NULL);
+
+  if (!g_variant_lookup (value, "version", "i", &version))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No version key");
+      goto out;
+    }
+  if (version != 1)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Cannot decode version %d data", version);
+      goto out;
+    }
+
+  if (!g_variant_lookup (value, "timestamp-usec", "x", &timestamp_usec))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No timestamp-usec");
+      goto out;
+    }
+
+  if (!g_variant_lookup (value, "device-size", "t", &device_size))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No device-size");
+      goto out;
+    }
+
+  if (!g_variant_lookup (value, "read-samples", "@a(td)", &read_samples_variant))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No read-samples");
+      goto out;
+    }
+
+  if (!g_variant_lookup (value, "write-samples", "@a(td)", &write_samples_variant))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No write-samples");
+      goto out;
+    }
+
+  if (!g_variant_lookup (value, "access-time-samples", "@a(td)", &access_time_samples_variant))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No access-time-samples");
+      goto out;
+    }
+
+  data->bm_time_benchmarked_usec = timestamp_usec;
+  data->bm_size = device_size;
+  samples_from_gvariant (data->bm_read_samples, read_samples_variant);
+  samples_from_gvariant (data->bm_write_samples, write_samples_variant);
+  samples_from_gvariant (data->bm_access_time_samples, access_time_samples_variant);
+
+  ret = TRUE;
+
+ out:
+  if (read_samples_variant != NULL)
+    g_variant_unref (read_samples_variant);
+  if (write_samples_variant != NULL)
+    g_variant_unref (write_samples_variant);
+  if (access_time_samples_variant != NULL)
+    g_variant_unref (access_time_samples_variant);
+  if (value != NULL)
+    g_variant_unref (value);
+  g_free (variant_data);
+  g_free (filename);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVariant *
+samples_to_gvariant (GArray *array)
+{
+  gint n;
+  GVariantBuilder builder;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(td)"));
+  for (n = 0; n < array->len; n++)
+    {
+      BMSample *s = &g_array_index (array, BMSample, n);
+      g_variant_builder_add (&builder, "(td)", s->offset, s->value);
+    }
+
+  return g_variant_builder_end (&builder);
+}
+
+
+static gboolean
+maybe_save_data (DialogData  *data,
+                 GError     **error)
+{
+  gboolean ret = FALSE;
+  gchar *filename = NULL;
+  GVariantBuilder builder;
+  GVariant *value = NULL;
+  gconstpointer variant_data;
+  gsize variant_size;
+
+  filename = get_bm_filename (data);
+  if (filename == NULL)
+    {
+      /* all good since we don't want to save data for this device */
+      ret = TRUE;
+      goto out;
+    }
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&builder, "{sv}", "version", g_variant_new_int32 (1));
+  g_variant_builder_add (&builder, "{sv}", "timestamp-usec", g_variant_new_int64 (data->bm_time_benchmarked_usec));
+  g_variant_builder_add (&builder, "{sv}", "device-size", g_variant_new_uint64 (data->bm_size));
+  g_variant_builder_add (&builder, "{sv}", "read-samples", samples_to_gvariant (data->bm_read_samples));
+  g_variant_builder_add (&builder, "{sv}", "write-samples", samples_to_gvariant (data->bm_write_samples));
+  g_variant_builder_add (&builder, "{sv}", "access-time-samples", samples_to_gvariant (data->bm_access_time_samples));
+  value = g_variant_builder_end (&builder);
+
+  variant_data = g_variant_get_data (value);
+  variant_size = g_variant_get_size (value);
+
+  if (!g_file_set_contents (filename,
+                            variant_data,
+                            variant_size,
+                            error))
+    goto out;
+
+ out:
+  if (value != NULL)
+    g_variant_unref (value);
+  g_free (filename);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called on main / UI thread */
+static gboolean
+bmt_on_timeout (gpointer user_data)
+{
+  DialogData *data = user_data;
+  update_dialog (data);
+  G_LOCK (bm_lock);
+  data->bm_update_timeout_pending = FALSE;
+  G_UNLOCK (bm_lock);
+  dialog_data_unref (data);
+  return FALSE; /* don't run again */
+}
+
+static void
+bmt_schedule_update (DialogData *data)
+{
+  /* rate-limit updates */
+  G_LOCK (bm_lock);
+  if (!data->bm_update_timeout_pending)
+    {
+      g_timeout_add (200, /* ms */
+                     bmt_on_timeout,
+                     dialog_data_ref (data));
+      data->bm_update_timeout_pending = TRUE;
+    }
+  G_UNLOCK (bm_lock);
+}
+
+static gpointer
+benchmark_thread (gpointer user_data)
+{
+  DialogData *data = user_data;
+  GVariant *fd_index = NULL;
+  GUnixFDList *fd_list = NULL;
+  GError *error = NULL;
+  guchar *buffer_unaligned = NULL;
+  guchar *buffer = NULL;
+  GRand *rand = NULL;
+  int fd = -1;
+  guint n;
+  long page_size;
+  guint64 disk_size;
+  GVariantBuilder options_builder;
+
+  //g_print ("bm thread start\n");
+
+  g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&options_builder, "{sv}", "writable", g_variant_new_boolean (data->bm_do_write));
+
+  if (!udisks_block_call_open_for_benchmark_sync (data->block,
+                                                  g_variant_builder_end (&options_builder),
+                                                  NULL, /* fd_list */
+                                                  &fd_index,
+                                                  &fd_list,
+                                                  data->bm_cancellable,
+                                                  &error))
+    goto out;
+
+  fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_index), NULL);
+  g_clear_object (&fd_list);
+
+  /* We can't use udisks_block_get_size() because the media may have
+   * changed and udisks may not have noticed. TODO: maybe have a
+   * Block.GetSize() method instead...
+   */
+  if (ioctl (fd, BLKGETSIZE64, &disk_size) != 0)
+    {
+      g_set_error (&error,
+                   G_IO_ERROR,
+                   g_io_error_from_errno (errno),
+                   C_("benchmarking", "Error getting size of device: %m"));
+      goto out;
+    }
+
+  page_size = sysconf (_SC_PAGESIZE);
+  if (page_size < 1)
+    {
+      g_set_error (&error,
+                   G_IO_ERROR,
+                   g_io_error_from_errno (errno),
+                   C_("benchmarking", "Error getting page size: %m\n"));
+      goto out;
+    }
+
+  buffer_unaligned = g_new0 (guchar, data->bm_sample_size_mib*1024*1024 + page_size);
+  buffer = (guchar*) (((gintptr) (buffer_unaligned + page_size)) & (~(page_size - 1)));
+
+  /* transfer rate... */
+  G_LOCK (bm_lock);
+  data->bm_size = disk_size;
+  data->bm_state = BM_STATE_TRANSFER_RATE;
+  G_UNLOCK (bm_lock);
+  for (n = 0; n < data->bm_num_samples; n++)
+    {
+      gint64 begin_usec;
+      gint64 end_usec;
+      guint64 offset;
+      ssize_t num_read;
+      BMSample sample = {0};
+
+      if (g_cancellable_set_error_if_cancelled (data->bm_cancellable, &error))
+        goto out;
+
+      /* figure out offset and align to page-size */
+      offset = n * disk_size / data->bm_num_samples;
+      offset &= ~(page_size - 1);
+
+      if (lseek (fd, offset, SEEK_SET) != offset)
+        {
+          g_set_error (&error,
+                       G_IO_ERROR,
+                       g_io_error_from_errno (errno),
+                       C_("benchmarking", "Error seeking to offset %lld"),
+                       (long long int) offset);
+          goto out;
+        }
+      if (read (fd, buffer, page_size) != page_size)
+        {
+          g_set_error (&error,
+                       G_IO_ERROR,
+                       g_io_error_from_errno (errno),
+                       C_("benchmarking", "Error pre-reading %lld bytes from offset %lld"),
+                       (long long int) page_size,
+                       (long long int) offset);
+          goto out;
+        }
+      if (lseek (fd, offset, SEEK_SET) != offset)
+        {
+          g_set_error (&error,
+                       G_IO_ERROR,
+                       g_io_error_from_errno (errno),
+                       C_("benchmarking", "Error seeking to offset %lld"),
+                       (long long int) offset);
+          goto out;
+        }
+      begin_usec = g_get_monotonic_time ();
+      num_read = read (fd, buffer, data->bm_sample_size_mib*1024*1024);
+      if (G_UNLIKELY (num_read < 0))
+        {
+          g_set_error (&error,
+                       G_IO_ERROR,
+                       g_io_error_from_errno (errno),
+                       C_("benchmarking", "Error reading %d MB from offset %lld"),
+                       data->bm_sample_size_mib,
+                       (long long int) offset);
+          goto out;
+        }
+      end_usec = g_get_monotonic_time ();
+
+      sample.offset = offset;
+      sample.value = ((gdouble) G_USEC_PER_SEC) * num_read / (end_usec - begin_usec);
+      G_LOCK (bm_lock);
+      g_array_append_val (data->bm_read_samples, sample);
+      G_UNLOCK (bm_lock);
+
+      bmt_schedule_update (data);
+
+      if (data->bm_do_write)
+        {
+          ssize_t num_written;
+
+          /* and now write the same block again... */
+          if (lseek (fd, offset, SEEK_SET) != offset)
+            {
+              g_set_error (&error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           C_("benchmarking", "Error seeking to offset %lld"),
+                           (long long int) offset);
+              goto out;
+            }
+          if (read (fd, buffer, page_size) != page_size)
+            {
+              g_set_error (&error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           C_("benchmarking", "Error pre-reading %lld bytes from offset %lld"),
+                           (long long int) page_size,
+                           (long long int) offset);
+              goto out;
+            }
+          if (lseek (fd, offset, SEEK_SET) != offset)
+            {
+              g_set_error (&error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           C_("benchmarking", "Error seeking to offset %lld"),
+                           (long long int) offset);
+              goto out;
+            }
+          begin_usec = g_get_monotonic_time ();
+          num_written = write (fd, buffer, num_read);
+          if (G_UNLIKELY (num_written < 0))
+            {
+              g_set_error (&error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           C_("benchmarking", "Error writing %lld bytes at offset %lld: %m"),
+                           (long long int) num_read,
+                           (long long int) offset);
+              goto out;
+            }
+          if (num_written != num_read)
+            {
+              g_set_error (&error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           C_("benchmarking", "Expected to write %lld bytes, only wrote %lld: %m"),
+                           (long long int) num_read,
+                           (long long int) num_written);
+              goto out;
+            }
+          if (fsync (fd) != 0)
+            {
+              g_set_error (&error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           C_("benchmarking", "Error syncing (at offset %lld): %m"),
+                           (long long int) offset);
+              goto out;
+            }
+          end_usec = g_get_monotonic_time ();
+
+          sample.offset = offset;
+          sample.value = ((gdouble) G_USEC_PER_SEC) * num_written / (end_usec - begin_usec);
+          G_LOCK (bm_lock);
+          g_array_append_val (data->bm_write_samples, sample);
+          G_UNLOCK (bm_lock);
+
+          bmt_schedule_update (data);
+        }
+    }
+
+  /* access time... */
+  G_LOCK (bm_lock);
+  data->bm_state = BM_STATE_ACCESS_TIME;
+  G_UNLOCK (bm_lock);
+  rand = g_rand_new_with_seed (42); /* want this to be deterministic (per size) so it's repeatable */
+  for (n = 0; n < data->bm_num_access_samples; n++)
+    {
+      gint64 begin_usec;
+      gint64 end_usec;
+      guint64 offset;
+      ssize_t num_read;
+      BMSample sample = {0};
+
+      if (g_cancellable_set_error_if_cancelled (data->bm_cancellable, &error))
+        goto out;
+
+      offset = (guint64) g_rand_double_range (rand, 0, (gdouble) disk_size);
+      offset &= ~(page_size - 1);
+
+      if (lseek (fd, offset, SEEK_SET) != offset)
+        {
+          g_set_error (&error,
+                       G_IO_ERROR,
+                       g_io_error_from_errno (errno),
+                       C_("benchmarking", "Error seeking to offset %lld: %m"),
+                       (long long int) offset);
+          goto out;
+        }
+
+      begin_usec = g_get_monotonic_time ();
+      num_read = read (fd, buffer, page_size);
+      if (G_UNLIKELY (num_read < 0))
+        {
+          g_set_error (&error,
+                       G_IO_ERROR,
+                       g_io_error_from_errno (errno),
+                       C_("benchmarking", "Error reading %lld bytes from offset %lld"),
+                       (long long int) page_size,
+                       (long long int) offset);
+          goto out;
+        }
+      end_usec = g_get_monotonic_time ();
+
+      sample.offset = offset;
+      sample.value = (end_usec - begin_usec) / ((gdouble) G_USEC_PER_SEC);
+      G_LOCK (bm_lock);
+      g_array_append_val (data->bm_access_time_samples, sample);
+      G_UNLOCK (bm_lock);
+
+      bmt_schedule_update (data);
+    }
+
+  G_LOCK (bm_lock);
+  data->bm_time_benchmarked_usec = g_get_real_time ();
+  G_UNLOCK (bm_lock);
+  if (!maybe_save_data (data, &error))
+    goto out;
+
+ out:
+  if (rand != NULL)
+    g_rand_free (rand);
+  g_clear_object (&fd_list);
+
+  if (fd_index != NULL)
+    g_variant_unref (fd_index);
+  if (fd != -1)
+    close (fd);
+  g_free (buffer_unaligned);
+  data->bm_in_progress = FALSE;
+  data->bm_thread = NULL;
+  data->bm_state = BM_STATE_NONE;
+
+  if (error != NULL)
+    {
+      G_LOCK (bm_lock);
+      data->bm_error = error;
+      g_array_set_size (data->bm_read_samples, 0);
+      g_array_set_size (data->bm_write_samples, 0);
+      g_array_set_size (data->bm_access_time_samples, 0);
+      data->bm_time_benchmarked_usec = 0;
+      G_UNLOCK (bm_lock);
+    }
+
+  bmt_schedule_update (data);
+
+  dialog_data_unref (data);
+
+  //g_print ("bm thread end\n");
+  return NULL;
+}
+
+static void
+abort_benchmark (DialogData *data)
+{
+  g_cancellable_cancel (data->bm_cancellable);
+}
+
+static void
+start_benchmark (DialogData *data)
+{
+  GtkWidget *dialog;
+  GtkBuilder *builder = NULL;
+  GtkWidget *num_samples_spinbutton;
+  GtkWidget *sample_size_spinbutton;
+  GtkWidget *write_checkbutton;
+  GtkWidget *num_access_samples_spinbutton;
+  gint response;
+
+  g_assert (!data->bm_in_progress);
+  g_assert (data->bm_thread == NULL);
+  g_assert_cmpint (data->bm_state, ==, BM_STATE_NONE);
+
+  dialog = GTK_WIDGET (gdu_application_new_widget (gdu_window_get_application (data->window),
+                                                   "benchmark-dialog.ui",
+                                                   "dialog2",
+                                                   &builder));
+  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (data->dialog));
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+  num_samples_spinbutton = GTK_WIDGET (gtk_builder_get_object (builder, "num-samples-spinbutton"));
+  sample_size_spinbutton = GTK_WIDGET (gtk_builder_get_object (builder, "sample-size-spinbutton"));
+  write_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder, "write-checkbutton"));
+  num_access_samples_spinbutton = GTK_WIDGET (gtk_builder_get_object (builder, "num-access-samples-spinbutton"));
+
+  /* if device is read-only, uncheck the "perform write-test"
+   * check-button and also make it insensitive
+   */
+  if (udisks_block_get_read_only (data->block))
+    {
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (write_checkbutton), FALSE);
+      gtk_widget_set_sensitive (write_checkbutton, FALSE);
+    }
+
+  /* and scene... */
+  response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+  gtk_widget_hide (dialog);
+
+  if (response != GTK_RESPONSE_OK)
+    goto out;
+
+  data->bm_num_samples = gtk_spin_button_get_value (GTK_SPIN_BUTTON (num_samples_spinbutton));
+  data->bm_sample_size_mib = gtk_spin_button_get_value (GTK_SPIN_BUTTON (sample_size_spinbutton));
+  data->bm_do_write = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (write_checkbutton));
+  data->bm_num_access_samples = gtk_spin_button_get_value (GTK_SPIN_BUTTON (num_access_samples_spinbutton));
+
+  //g_print ("num_samples=%d\n", data->bm_num_samples);
+  //g_print ("sample_size=%d MB\n", data->bm_sample_size_mib);
+  //g_print ("do_write=%d\n", data->bm_do_write);
+  //g_print ("num_access_samples=%d\n", data->bm_num_access_samples);
+
+  data->bm_in_progress = TRUE;
+  data->bm_state = BM_STATE_OPENING_DEVICE;
+  g_clear_error (&data->bm_error);
+  g_array_set_size (data->bm_read_samples, 0);
+  g_array_set_size (data->bm_write_samples, 0);
+  g_array_set_size (data->bm_access_time_samples, 0);
+  data->bm_time_benchmarked_usec = 0;
+  g_cancellable_reset (data->bm_cancellable);
+
+  data->bm_thread = g_thread_new ("benchmark-thread",
+                                  benchmark_thread,
+                                  dialog_data_ref (data));
+
+ out:
+  gtk_widget_destroy (dialog);
+  g_clear_object (&builder);
+  update_dialog (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+gdu_benchmark_dialog_show (GduWindow    *window,
+                           UDisksObject *object)
+{
+  DialogData *data;
+  guint n;
+  guint timeout_id;
+  GError *error = NULL;
+
+  data = g_new0 (DialogData, 1);
+  data->ref_count = 1;
+  data->object = g_object_ref (object);
+  data->block = udisks_object_peek_block (data->object);
+  data->window = g_object_ref (window);
+  data->bm_cancellable = g_cancellable_new ();
+
+  data->bm_read_samples = g_array_new (FALSE, /* zero-terminated */
+                                       FALSE, /* clear */
+                                       sizeof (BMSample));
+  data->bm_write_samples = g_array_new (FALSE, /* zero-terminated */
+                                        FALSE, /* clear */
+                                        sizeof (BMSample));
+  data->bm_access_time_samples = g_array_new (FALSE, /* zero-terminated */
+                                              FALSE, /* clear */
+                                              sizeof (BMSample));
+
+  data->dialog = GTK_WIDGET (gdu_application_new_widget (gdu_window_get_application (window),
+                                                         "benchmark-dialog.ui",
+                                                         "dialog1",
+                                                         &data->builder));
+  for (n = 0; widget_mapping[n].name != NULL; n++)
+    {
+      gpointer *p = (gpointer *) ((char *) data + widget_mapping[n].offset);
+      *p = GTK_WIDGET (gtk_builder_get_object (data->builder, widget_mapping[n].name));
+    }
+
+
+  gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (window));
+
+  data->start_benchmark_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), 0);
+  data->stop_benchmark_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), 1);
+
+  g_signal_connect (data->graph_drawing_area,
+                    "draw",
+                    G_CALLBACK (on_drawing_area_draw),
+                    data);
+
+  /* set minimum size for the graph */
+  gtk_widget_set_size_request (data->graph_drawing_area,
+                               600,
+                               300);
+
+  /* need this to update the "Updated" value */
+  timeout_id = g_timeout_add_seconds (1, on_timeout, data);
+
+  /* see if we have cached data */
+  if (!maybe_load_data (data, &error))
+    {
+      /* not worth complaining in dialog about */
+      g_warning ("Error loading cached data: %s (%s, %d)",
+                 error->message, g_quark_to_string (error->domain), error->code);
+      g_clear_error (&error);
+    }
+
+  update_dialog (data);
+
+  while (TRUE)
+    {
+      gint response;
+      response = gtk_dialog_run (GTK_DIALOG (data->dialog));
+      /* Keep in sync with .ui file */
+      switch (response)
+        {
+        case 0: /* start benchmark */
+          start_benchmark (data);
+          break;
+
+        case 1: /* abort benchmark */
+          abort_benchmark (data);
+          break;
+        }
+
+      if (response < 0)
+        break;
+    }
+
+  g_source_remove (timeout_id);
+  dialog_data_close (data);
+}
diff --git a/src/disks/gdubenchmarkdialog.h b/src/disks/gdubenchmarkdialog.h
new file mode 100644
index 0000000..023f427
--- /dev/null
+++ b/src/disks/gdubenchmarkdialog.h
@@ -0,0 +1,36 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __GDU_BENCHMARK_DIALOG_H__
+#define __GDU_BENCHMARK_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "gdutypes.h"
+
+G_BEGIN_DECLS
+
+void   gdu_benchmark_dialog_show (GduWindow    *window,
+                                  UDisksObject *object);
+
+G_END_DECLS
+
+#endif /* __GDU_BENCHMARK_DIALOG_H__ */
diff --git a/src/disks/gduutils.c b/src/disks/gduutils.c
index 67e6712..807b15e 100644
--- a/src/disks/gduutils.c
+++ b/src/disks/gduutils.c
@@ -124,7 +124,7 @@ gdu_utils_duration_to_string (guint    duration_sec,
                                         duration_sec / 60),
                            duration_sec / 60);
     }
-  else
+  else if (duration_sec < 86400)
     {
       s = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
                                         "%d hour",
@@ -132,6 +132,14 @@ gdu_utils_duration_to_string (guint    duration_sec,
                                         duration_sec / 3600),
                            duration_sec / 3600);
     }
+  else
+    {
+      s = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
+                                        "%d day",
+                                        "%d days",
+                                        duration_sec / 86400),
+                           duration_sec / 86400);
+    }
   return s;
 }
 
diff --git a/src/disks/gduwindow.c b/src/disks/gduwindow.c
index bbafd18..525179d 100644
--- a/src/disks/gduwindow.c
+++ b/src/disks/gduwindow.c
@@ -38,6 +38,7 @@
 #include "gduutils.h"
 #include "gduvolumegrid.h"
 #include "gduatasmartdialog.h"
+#include "gdubenchmarkdialog.h"
 #include "gducrypttabdialog.h"
 #include "gdufstabdialog.h"
 #include "gdufilesystemdialog.h"
@@ -111,6 +112,7 @@ struct _GduWindow
   GtkWidget *generic_drive_menu_item_format_disk;
   GtkWidget *generic_drive_menu_item_create_disk_image;
   GtkWidget *generic_drive_menu_item_restore_disk_image;
+  GtkWidget *generic_drive_menu_item_benchmark;
 
   GtkWidget *generic_menu;
   GtkWidget *generic_menu_item_configure_fstab;
@@ -121,6 +123,7 @@ struct _GduWindow
   GtkWidget *generic_menu_item_format_volume;
   GtkWidget *generic_menu_item_create_volume_image;
   GtkWidget *generic_menu_item_restore_volume_image;
+  GtkWidget *generic_menu_item_benchmark;
 
   GtkWidget *devtab_loop_autoclear_switch;
 
@@ -168,6 +171,7 @@ static const struct {
   {G_STRUCT_OFFSET (GduWindow, generic_drive_menu), "generic-drive-menu"},
   {G_STRUCT_OFFSET (GduWindow, generic_drive_menu_item_create_disk_image), "generic-drive-menu-item-create-disk-image"},
   {G_STRUCT_OFFSET (GduWindow, generic_drive_menu_item_restore_disk_image), "generic-drive-menu-item-restore-disk-image"},
+  {G_STRUCT_OFFSET (GduWindow, generic_drive_menu_item_benchmark), "generic-drive-menu-item-benchmark"},
   {G_STRUCT_OFFSET (GduWindow, generic_drive_menu_item_view_smart), "generic-drive-menu-item-view-smart"},
   {G_STRUCT_OFFSET (GduWindow, generic_drive_menu_item_format_disk), "generic-drive-menu-item-format-disk"},
 
@@ -180,6 +184,7 @@ static const struct {
   {G_STRUCT_OFFSET (GduWindow, generic_menu_item_format_volume), "generic-menu-item-format-volume"},
   {G_STRUCT_OFFSET (GduWindow, generic_menu_item_create_volume_image), "generic-menu-item-create-volume-image"},
   {G_STRUCT_OFFSET (GduWindow, generic_menu_item_restore_volume_image), "generic-menu-item-restore-volume-image"},
+  {G_STRUCT_OFFSET (GduWindow, generic_menu_item_benchmark), "generic-menu-item-benchmark"},
 
   {0, NULL}
 };
@@ -217,20 +222,22 @@ typedef enum
   SHOW_FLAGS_ENCRYPTED_LOCK_BUTTON   = (1<<9),
 
   /* generic drive menu */
-  SHOW_FLAGS_DISK_POPUP_MENU_CREATE_DISK_IMAGE     = (1<<20),
-  SHOW_FLAGS_DISK_POPUP_MENU_RESTORE_DISK_IMAGE    = (1<<21),
-  SHOW_FLAGS_DISK_POPUP_MENU_VIEW_SMART            = (1<<22),
-  SHOW_FLAGS_DISK_POPUP_MENU_FORMAT_DISK           = (1<<23),
+  SHOW_FLAGS_DISK_POPUP_MENU_CREATE_DISK_IMAGE     = (1<<10),
+  SHOW_FLAGS_DISK_POPUP_MENU_RESTORE_DISK_IMAGE    = (1<<11),
+  SHOW_FLAGS_DISK_POPUP_MENU_BENCHMARK             = (1<<12),
+  SHOW_FLAGS_DISK_POPUP_MENU_VIEW_SMART            = (1<<13),
+  SHOW_FLAGS_DISK_POPUP_MENU_FORMAT_DISK           = (1<<14),
 
   /* generic volume menu */
-  SHOW_FLAGS_POPUP_MENU_CONFIGURE_FSTAB       = (1<<24),
-  SHOW_FLAGS_POPUP_MENU_CONFIGURE_CRYPTTAB    = (1<<25),
-  SHOW_FLAGS_POPUP_MENU_CHANGE_PASSPHRASE     = (1<<26),
-  SHOW_FLAGS_POPUP_MENU_EDIT_LABEL            = (1<<27),
-  SHOW_FLAGS_POPUP_MENU_EDIT_PARTITION        = (1<<28),
-  SHOW_FLAGS_POPUP_MENU_FORMAT_VOLUME         = (1<<29),
-  SHOW_FLAGS_POPUP_MENU_CREATE_VOLUME_IMAGE   = (1<<30),
-  SHOW_FLAGS_POPUP_MENU_RESTORE_VOLUME_IMAGE  = (1<<31),
+  SHOW_FLAGS_POPUP_MENU_CONFIGURE_FSTAB       = (1<<20),
+  SHOW_FLAGS_POPUP_MENU_CONFIGURE_CRYPTTAB    = (1<<21),
+  SHOW_FLAGS_POPUP_MENU_CHANGE_PASSPHRASE     = (1<<22),
+  SHOW_FLAGS_POPUP_MENU_EDIT_LABEL            = (1<<23),
+  SHOW_FLAGS_POPUP_MENU_EDIT_PARTITION        = (1<<24),
+  SHOW_FLAGS_POPUP_MENU_FORMAT_VOLUME         = (1<<25),
+  SHOW_FLAGS_POPUP_MENU_CREATE_VOLUME_IMAGE   = (1<<26),
+  SHOW_FLAGS_POPUP_MENU_RESTORE_VOLUME_IMAGE  = (1<<27),
+  SHOW_FLAGS_POPUP_MENU_BENCHMARK             = (1<<28),
 } ShowFlags;
 
 static void setup_device_page (GduWindow *window, UDisksObject *object);
@@ -260,6 +267,8 @@ static void on_generic_drive_menu_item_create_disk_image (GtkMenuItem *menu_item
                                                           gpointer   user_data);
 static void on_generic_drive_menu_item_restore_disk_image (GtkMenuItem *menu_item,
                                                            gpointer   user_data);
+static void on_generic_drive_menu_item_benchmark (GtkMenuItem *menu_item,
+                                                  gpointer   user_data);
 
 static void on_generic_menu_item_configure_fstab (GtkMenuItem *menu_item,
                                                   gpointer   user_data);
@@ -277,6 +286,8 @@ static void on_generic_menu_item_create_volume_image (GtkMenuItem *menu_item,
                                                       gpointer   user_data);
 static void on_generic_menu_item_restore_volume_image (GtkMenuItem *menu_item,
                                                        gpointer   user_data);
+static void on_generic_menu_item_benchmark (GtkMenuItem *menu_item,
+                                            gpointer   user_data);
 
 static void on_devtab_loop_autoclear_switch_notify_active (GObject    *object,
                                                            GParamSpec *pspec,
@@ -395,6 +406,8 @@ update_for_show_flags (GduWindow *window,
                             show_flags & SHOW_FLAGS_DISK_POPUP_MENU_CREATE_DISK_IMAGE);
   gtk_widget_set_sensitive (GTK_WIDGET (window->generic_drive_menu_item_restore_disk_image),
                             show_flags & SHOW_FLAGS_DISK_POPUP_MENU_RESTORE_DISK_IMAGE);
+  gtk_widget_set_sensitive (GTK_WIDGET (window->generic_drive_menu_item_benchmark),
+                            show_flags & SHOW_FLAGS_DISK_POPUP_MENU_BENCHMARK);
 
   gtk_widget_set_sensitive (GTK_WIDGET (window->generic_menu_item_configure_fstab),
                             show_flags & SHOW_FLAGS_POPUP_MENU_CONFIGURE_FSTAB);
@@ -412,6 +425,8 @@ update_for_show_flags (GduWindow *window,
                             show_flags & SHOW_FLAGS_POPUP_MENU_CREATE_VOLUME_IMAGE);
   gtk_widget_set_sensitive (GTK_WIDGET (window->generic_menu_item_restore_volume_image),
                             show_flags & SHOW_FLAGS_POPUP_MENU_RESTORE_VOLUME_IMAGE);
+  gtk_widget_set_sensitive (GTK_WIDGET (window->generic_menu_item_benchmark),
+                            show_flags & SHOW_FLAGS_POPUP_MENU_BENCHMARK);
   /* TODO: don't show the button bringing up the popup menu if it has no items */
 }
 
@@ -1090,6 +1105,10 @@ gdu_window_constructed (GObject *object)
                     "activate",
                     G_CALLBACK (on_generic_drive_menu_item_restore_disk_image),
                     window);
+  g_signal_connect (window->generic_drive_menu_item_benchmark,
+                    "activate",
+                    G_CALLBACK (on_generic_drive_menu_item_benchmark),
+                    window);
 
   /* volume menu */
   g_signal_connect (window->generic_menu_item_configure_fstab,
@@ -1124,6 +1143,10 @@ gdu_window_constructed (GObject *object)
                     "activate",
                     G_CALLBACK (on_generic_menu_item_restore_volume_image),
                     window);
+  g_signal_connect (window->generic_menu_item_benchmark,
+                    "activate",
+                    G_CALLBACK (on_generic_menu_item_benchmark),
+                    window);
 
   /* loop's auto-clear switch */
   g_signal_connect (window->devtab_loop_autoclear_switch,
@@ -1841,6 +1864,8 @@ update_device_page_for_block (GduWindow          *window,
   if (udisks_block_get_size (block) > 0 || (drive != NULL && !udisks_drive_get_media_change_detected (drive)))
     {
       *show_flags |= SHOW_FLAGS_POPUP_MENU_CREATE_VOLUME_IMAGE;
+      *show_flags |= SHOW_FLAGS_POPUP_MENU_BENCHMARK;
+      *show_flags |= SHOW_FLAGS_DISK_POPUP_MENU_BENCHMARK;
       *show_flags |= SHOW_FLAGS_DISK_POPUP_MENU_CREATE_DISK_IMAGE;
       if (!read_only)
         *show_flags |= SHOW_FLAGS_DISK_POPUP_MENU_RESTORE_DISK_IMAGE;
@@ -2100,6 +2125,7 @@ update_device_page_for_free_space (GduWindow          *window,
   read_only = udisks_block_get_read_only (block);
   loop = udisks_object_peek_loop (window->current_object);
 
+  *show_flags |= SHOW_FLAGS_DISK_POPUP_MENU_BENCHMARK;
   if (!read_only)
     {
       *show_flags |= SHOW_FLAGS_DISK_POPUP_MENU_FORMAT_DISK;
@@ -2393,6 +2419,20 @@ on_generic_drive_menu_item_restore_disk_image (GtkMenuItem *menu_item,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
+on_generic_drive_menu_item_benchmark (GtkMenuItem *menu_item,
+                                      gpointer   user_data)
+{
+  GduWindow *window = GDU_WINDOW (user_data);
+  UDisksObject *object;
+
+  object = gdu_volume_grid_get_block_object (GDU_VOLUME_GRID (window->volume_grid));
+  g_assert (object != NULL);
+  gdu_benchmark_dialog_show (window, object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
 on_generic_menu_item_create_volume_image (GtkMenuItem *menu_item,
                                           gpointer   user_data)
 {
@@ -2421,6 +2461,20 @@ on_generic_menu_item_restore_volume_image (GtkMenuItem *menu_item,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
+on_generic_menu_item_benchmark (GtkMenuItem *menu_item,
+                                gpointer   user_data)
+{
+  GduWindow *window = GDU_WINDOW (user_data);
+  UDisksObject *object;
+
+  object = gdu_volume_grid_get_selected_device (GDU_VOLUME_GRID (window->volume_grid));
+  g_assert (object != NULL);
+  gdu_benchmark_dialog_show (window, object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
 on_generic_drive_menu_item_format_disk (GtkMenuItem *menu_item,
                                         gpointer   user_data)
 {



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]