Skip to contentSkip to conversation

Block script performance with shared image files

PATCH Superseded Mike Latimer 3 participants 7 messages Sep 30, 2015 – Oct 1, 2015

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

cover Block script performance with shared image files

As mentioned in a previous thread[1], the hotplug block script suffers from an exponential performance degredation when attaching shared image files due to scanning xenstore multiple times.

During the attachment of a loopback mounted image file, the mode of all curent instances of this device already attached to other domains must be checked. This requires finding all loopback devices pointing to the inode of the shared image file, and then comparing the major and minor number of these devices to the major and minor number of every vbd device found in the xenstore database. Scanning all of xenstore is performed for every instance of every loopback device pointing to the same shared image file, and becomes slower and slower with every additional instance.

Rather than scanning the entire xenstore database for every instance of a shared loopback device, the attached patch creates a list of the major and minor numbers from all matching loopback devices. Xenstore is then walked once, and major and minor numbers from every vbd are checked against the list.

I think this is a reasonable approach to this issue, and the benefits can be seen with as few as 10 domains sharing the same image file.

Thanks,
Mike

[1]http://lists.xenproject.org/archives/html/xen-devel/2015-09/msg03551.html

Mike Latimer (1):
tools/hotplug: Scan xenstore once when attaching shared images files

 tools/hotplug/Linux/block | 67 +++++++++++++++++++++++++++++------------------
 1 file changed, 41 insertions(+), 26 deletions(-)
Signature · 1 line
1.8.4.5
#1 lore raw

PATCH 1/1 tools/hotplug: Scan xenstore once when attaching shared images files

Create the list of shared loopback devices from within check_sharing, rather than calling check_sharing for every loopback device using the same shared image.

This change prevents the xenstore database from being walked for every shared device, which causes an exponential decrease in performance.

---
 tools/hotplug/Linux/block | 67 +++++++++++++++++++++++++++++------------------
 1 file changed, 41 insertions(+), 26 deletions(-)
tools/hotplug/Linux/block +41 -26
index 8d2ee9d..aef051c 100644
@@ -38,7 +38,7 @@ find_free_loopback_dev() {
 }
 
 ##
-# check_sharing device mode
+# check_sharing devtype device mode [inode]
 #
 # Check whether the device requested is already in use.  To use the device in
 # read-only mode, it may be in use in read-only mode, but may not be in use in
@@ -56,10 +56,27 @@ find_free_loopback_dev() {
 #
 check_sharing()
 {
-  local dev="$1"
-  local mode="$2"
+  local devtype=$1
+  local dev="$2"
+  local mode="$3"
+
+  if [ "$devtype" = "file" ];
+  then
+    local inode="$4"
+
+    shared_list=$(losetup -a |
+          sed -n -e "s@^\([^:]\+\)\(:[[:blank:]]\[0*${dev}\]:${inode}[[:blank:]](.*)\)@\1@p" )
+    for dev in $shared_list
+    do
+      if [ -n "$dev" ]
+      then
+        devmm="${devmm}$(device_major_minor $dev),"
+      fi
+    done
+  else
+    local devmm="$(device_major_minor "$dev"),"
+  fi
 
-  local devmm=$(device_major_minor "$dev")
   local file
 
   if [ "$mode" = 'w' ]
@@ -75,9 +92,9 @@ check_sharing()
     then
       local d=$(device_major_minor "$file")
 
-      if [ "$d" = "$devmm" ]
+      if [[ "$devmm" == *"$d,"* ]]
       then
-        echo 'local'
+        echo "local $d"
         return
       fi
     fi
@@ -90,13 +107,13 @@ check_sharing()
     do
       d=$(xenstore_read_default "$base_path/$dom/$dev/physical-device" "")
 
-      if [ "$d" = "$devmm" ]
+      if [ -n "$d" ] && [[ "$devmm" == *"$d,"* ]]
       then
         if [ "$mode" = 'w' ]
         then
           if ! same_vm $dom
           then
-            echo 'guest'
+            echo "guest $d"
             return
           fi
         else
@@ -107,7 +124,7 @@ check_sharing()
           then
             if ! same_vm $dom
             then
-              echo 'guest'
+              echo "guest $d"
               return
             fi
           fi
@@ -129,6 +146,7 @@ check_device_sharing()
 {
   local dev="$1"
   local mode=$(canonicalise_mode "$2")
+  local type="device"
   local result
 
   if [ "x$mode" = 'x!' ]
@@ -136,33 +154,38 @@ check_device_sharing()
     return 0
   fi
 
-  result=$(check_sharing "$dev" "$mode")
+  result=$(check_sharing "$type" "$dev" "$mode")
 
   if [ "$result" != 'ok' ]
   then
-    do_ebusy "Device $dev is mounted " "$mode" "$result"
+    do_ebusy "Device $dev is mounted " "$mode" "${result%% *}"
   fi
 }
 
 
 ##
-# check_device_sharing file dev mode
+# check_device_sharing file dev mode inode
 #
-# Perform the sharing check for the given file mounted through the given
-# loopback interface, in the given mode.
+# Perform the sharing check for the given file, with its corresponding
+# device, inode and mode. As the file can be mounted multiple times,
+# the inode is passed through to check_sharing for all instances to be
+# checked.
 #
 check_file_sharing()
 {
   local file="$1"
   local dev="$2"
   local mode="$3"
+  local inode="$4"
+  local type="file"
+  local result
 
-  result=$(check_sharing "$dev" "$mode")
+  result=$(check_sharing "$type" "$dev" "$mode" "$inode")
 
   if [ "$result" != 'ok' ]
   then
-    do_ebusy "File $file is loopback-mounted through $dev,
-which is mounted " "$mode" "$result"
+    do_ebusy "File $file is loopback-mounted through ${result#* },
+which is mounted " "$mode" "${result%% *}"
   fi
 }
 
@@ -281,15 +304,7 @@ mount it read-write in a guest domain."
             fatal "Unable to lookup $file: dev: $dev inode: $inode"
           fi
 
-          shared_list=$(losetup -a |
-                sed -n -e "s@^\([^:]\+\)\(:[[:blank:]]\[0*${dev}\]:${inode}[[:blank:]](.*)\)@\1@p" )
-          for dev in $shared_list
-          do
-            if [ -n "$dev" ]
-            then
-              check_file_sharing "$file" "$dev" "$mode"
-            fi
-          done
+          check_file_sharing "$file" "$dev" "$mode" "$inode"
         fi
 
         loopdev=$(losetup -f 2>/dev/null || find_free_loopback_dev)
Signature · 1 line
1.8.4.5
#2 lore raw

Re: [PATCH 1/1] tools/hotplug: Scan xenstore once when attaching shared images files

CC'ing relevant maintainers, and someone who's worked with block scripts before...

Quoted 8 lines · Mike Latimer

Create the list of shared loopback devices from within check_sharing, rather than calling check_sharing for every loopback device using the same shared image.

This change prevents the xenstore database from being walked for every shared device, which causes an exponential decrease in performance.

Thanks for trying to address this -- this has obviously been a bit of a pain for a while. A couple of comments:

Quoted 28 lines
---
 tools/hotplug/Linux/block | 67 +++++++++++++++++++++++++++++------------------
 1 file changed, 41 insertions(+), 26 deletions(-)
tools/hotplug/Linux/block +6 -3
index 8d2ee9d..aef051c 100644
@@ -38,7 +38,7 @@ find_free_loopback_dev() {
 }
 
 ##
-# check_sharing device mode
+# check_sharing devtype device mode [inode]
 #
 # Check whether the device requested is already in use.  To use the device in
 # read-only mode, it may be in use in read-only mode, but may not be in use in
@@ -56,10 +56,27 @@ find_free_loopback_dev() {
 #
 check_sharing()
 {
-  local dev="$1"
-  local mode="$2"
+  local devtype=$1
+  local dev="$2"
+  local mode="$3"
+
+  if [ "$devtype" = "file" ];

tools/hotplug/Linux/block find_free_loopback_dev() { · line 73

Quoted 10 lines
tools/hotplug/Linux/block +10 -0
+  then
+    local inode="$4"
+
+    shared_list=$(losetup -a |
+          sed -n -e "s@^\([^:]\+\)\(:[[:blank:]]\[0*${dev}\]:${inode}[[:blank:]](.*)\)@\1@p" )
+    for dev in $shared_list
+    do
+      if [ -n "$dev" ]
+      then
+        devmm="${devmm}$(device_major_minor $dev),"

You forgot to declare devmm local. Since devmm is declared and used on both sides of the 'if', it seems like you should probably declare it local up top, rather than when it's first used.

Quoted 11 lines
tools/hotplug/Linux/block +5 -1
+      fi
+    done
+  else
+    local devmm="$(device_major_minor "$dev"),"
+  fi
 
-  local devmm=$(device_major_minor "$dev")
   local file
 
   if [ "$mode" = 'w' ]

tools/hotplug/Linux/block check_sharing() · line 95

Quoted 5 lines
tools/hotplug/Linux/block +1 -1
@@ -75,9 +92,9 @@ check_sharing()
     then
       local d=$(device_major_minor "$file")
 
-      if [ "$d" = "$devmm" ]
+      if [[ "$devmm" == *"$d,"* ]]

Style nit: using [[ instead of [. TBH I prefer [[, but it's probably better to be consistent with the rest of the file.

tools/hotplug/Linux/block check_sharing() · line 110

Quoted 12 lines
tools/hotplug/Linux/block +2 -2
       then
-        echo 'local'
+        echo "local $d"
         return
       fi
     fi
@@ -90,13 +107,13 @@ check_sharing()
     do
       d=$(xenstore_read_default "$base_path/$dom/$dev/physical-device" "")
 
-      if [ "$d" = "$devmm" ]
+      if [ -n "$d" ] && [[ "$devmm" == *"$d,"* ]]

Is there a risk here of aliasing? i.e., suppose that a device with major-minor '11:1' that's already in use by a guest, and you're checking to see if you can share device '1:1' with a different guest. Won't this match, and (falsely) reject 1:1, even though it's not being used by anyone else?

Would it make more sense maybe to initialize devmm to ",", and then search for *",$d,"*?

Also, [[ again.

Other than that, seems good to me.

-George

#3 lore raw

Re: [PATCH 1/1] tools/hotplug: Scan xenstore once when attaching shared images files

Quoted 73 lines · George Dunlap

CC'ing relevant maintainers, and someone who's worked with block scripts before...

Quoted 8 lines · Mike Latimer

Create the list of shared loopback devices from within check_sharing, rather than calling check_sharing for every loopback device using the same shared image.

This change prevents the xenstore database from being walked for every shared device, which causes an exponential decrease in performance.

Thanks for trying to address this -- this has obviously been a bit of a pain for a while. A couple of comments:

Quoted 28 lines
---
 tools/hotplug/Linux/block | 67 +++++++++++++++++++++++++++++------------------
 1 file changed, 41 insertions(+), 26 deletions(-)
tools/hotplug/Linux/block +6 -3
index 8d2ee9d..aef051c 100644
@@ -38,7 +38,7 @@ find_free_loopback_dev() {
 }
 
 ##
-# check_sharing device mode
+# check_sharing devtype device mode [inode]
 #
 # Check whether the device requested is already in use.  To use the device in
 # read-only mode, it may be in use in read-only mode, but may not be in use in
@@ -56,10 +56,27 @@ find_free_loopback_dev() {
 #
 check_sharing()
 {
-  local dev="$1"
-  local mode="$2"
+  local devtype=$1
+  local dev="$2"
+  local mode="$3"
+
+  if [ "$devtype" = "file" ];

tools/hotplug/Linux/block find_free_loopback_dev() { · line 73

Quoted 10 lines
tools/hotplug/Linux/block +10 -0
+  then
+    local inode="$4"
+
+    shared_list=$(losetup -a |
+          sed -n -e "s@^\([^:]\+\)\(:[[:blank:]]\[0*${dev}\]:${inode}[[:blank:]](.*)\)@\1@p" )
+    for dev in $shared_list
+    do
+      if [ -n "$dev" ]
+      then
+        devmm="${devmm}$(device_major_minor $dev),"

You forgot to declare devmm local. Since devmm is declared and used on both sides of the 'if', it seems like you should probably declare it local up top, rather than when it's first used.

Quoted 14 lines
tools/hotplug/Linux/block +5 -1
+      fi
+    done
+  else
+    local devmm="$(device_major_minor "$dev"),"
+  fi
 
-  local devmm=$(device_major_minor "$dev")
   local file
 
   if [ "$mode" = 'w' ]
@@ -75,9 +92,9 @@ check_sharing()
     then
       local d=$(device_major_minor "$file")
 
Quoted 5 lines

tools/hotplug/Linux/block check_sharing() · line 95

Quoted 2 lines
tools/hotplug/Linux/block +1 -1
-      if [ "$d" = "$devmm" ]
+      if [[ "$devmm" == *"$d,"* ]]

Style nit: using [[ instead of [. TBH I prefer [[, but it's probably better to be consistent with the rest of the file.

Obviously the check here should be *",$d,"* as well.

BTW my experiments bear out the aliasing issue, and my proposed solution:

$ a="11:1,12:1,13:1"
$ if [[ "$a" == *"1:1,"* ]] ; then echo match ; fi
match
$ a=",11:1,12:1,13:1"
$ if [[ "$a" == *",1:1,"* ]] ; then echo match ; fi
$ if [[ "$a" == *",11:1,"* ]] ; then echo match ; fi
match

-George

#4 lore raw

Re: [PATCH 1/1] tools/hotplug: Scan xenstore once when attaching shared images files

In reply to George Dunlap

Hi George,

Quoted 9 lines · George Dunlap

tools/hotplug/Linux/block check_sharing() · line 108

Quoted 9 lines

then

tools/hotplug/Linux/block +1 -1
-        echo 'local'
+        echo "local $d"
         return
       fi
     fi
@@ -90,13 +107,13 @@ check_sharing()
     do
       d=$(xenstore_read_default "$base_path/$dom/$dev/physical-device" 

"")

Quoted 4 lines

tools/hotplug/Linux/block check_sharing() · line 110

Quoted 3 lines
tools/hotplug/Linux/block +1 -1
-      if [ "$d" = "$devmm" ]
+      if [ -n "$d" ] && [[ "$devmm" == *"$d,"* ]]
Quoted 8 lines

Is there a risk here of aliasing? i.e., suppose that a device with major-minor '11:1' that's already in use by a guest, and you're checking to see if you can share device '1:1' with a different guest. Won't this match, and (falsely) reject 1:1, even though it's not being used by anyone else?

Would it make more sense maybe to initialize devmm to ",", and then search for *",$d,"*?

Ah, thanks for catching that! I caught the "1:11" case, but somehow missed the "11:1" side.

I'll address that, and your other comments and submit a V2 shortly.

-Mike

#5 lore raw

Re: [PATCH 1/1] tools/hotplug: Scan xenstore once when attaching shared images files

In reply to George Dunlap

Hi again,

Quoted 6 lines · George Dunlap

tools/hotplug/Linux/block check_sharing() · line 95

Quoted 3 lines
tools/hotplug/Linux/block +1 -1
-      if [ "$d" = "$devmm" ]
+      if [[ "$devmm" == *"$d,"* ]]

Style nit: using [[ instead of [. TBH I prefer [[, but it's probably better to be consistent with the rest of the file.

I was about to change this to something like:

  if [ "$devmm" != ${devmm/$d/} ]

but I'm a bit concerned about the potential size of the variables involved. If there are 500 devices with 5-7 characters per device, $devmm becomes a pretty large string. I'm not sure this bash substitution method is a great option. I also don't really want the hit of passing the variable to grep.

According to one post I found benchmarking various types of tests [1], the two fastest options are:

[[ $b == *$a* ]]

case $b in *$a):;;esac

I can implement a case statement, but that seems even less clean than the simple [[ ... ]] approach (since there is only one case we care about).

As this is a #!/bin/bash script, is [[ ... ]] okay to use, or would you prefer to use the case statement? (If you have any other ideas, I'm open to that as well.)

Thanks!
Mike

[1]http://stackoverflow.com/questions/229551/string-contains-in-bash (search for 'fastest'

#6 lore raw

Re: [PATCH 1/1] tools/hotplug: Scan xenstore once when attaching shared images files

Quoted 24 lines · Mike Latimer

Hi again,

Quoted 6 lines · George Dunlap

tools/hotplug/Linux/block check_sharing() · line 95

Quoted 3 lines
tools/hotplug/Linux/block +1 -1
-      if [ "$d" = "$devmm" ]
+      if [[ "$devmm" == *"$d,"* ]]

Style nit: using [[ instead of [. TBH I prefer [[, but it's probably better to be consistent with the rest of the file.

I was about to change this to something like:

  if [ "$devmm" != ${devmm/$d/} ]

but I'm a bit concerned about the potential size of the variables involved. If there are 500 devices with 5-7 characters per device, $devmm becomes a pretty large string. I'm not sure this bash substitution method is a great option. I also don't really want the hit of passing the variable to grep.

According to one post I found benchmarking various types of tests [1], the two fastest options are:

[[ $b == *$a* ]]

Quoted 8 lines

case $b in *$a):;;esac

I can implement a case statement, but that seems even less clean than the simple [[ ... ]] approach (since there is only one case we care about).

As this is a #!/bin/bash script, is [[ ... ]] okay to use, or would you prefer to use the case statement? (If you have any other ideas, I'm open to that as well.)

Oh, right -- sorry, I didn't realize that the test you were using was only available with [[ ]].

The maintainers (of which I'm not one) have the final say. I'd personally be in favor of leaving it [[ ]] with a comment before each one saying that the $b == *$a* format is only available in [[ ]], so that no one comes later and "cleans it up".

I'd personally be even more happy if all the [ ] in the script were magically changed to [[ ]]. :-)

-George