Hi,
I scribbled a simple guestfs based program called guestfs-xfer with
following synopsis:
Usage: guest-xfer [options] [op] [diskimage]
op = [ls|cat|write]
options:
-d, --guestdevice DEV guest device
--blocksize BS blocksize [default: 1048576]
-o, --offset OFF offset [default: 0]
So eg. `cat /dev/urandom | guest-xfer -d /dev/sda write mydisk.img` will fill
mydisk.img with pseudorandom content.
I implemented this both with Ruby and Go. The 'write' op relies on
pwrite_device.
I have pasted the codes to the end of this mail.
I'm creating mydisk.img as a qcow2 file with a raw sparse file bakcend
$ truncate -s 100g myimg.raw
$ qemu-img create -f qcow2 -b myimg.{raw,img}
Then I do
# pv /dev/sda2 | guest-xfer -d /dev/sda write mydisk.img
I find that the ruby implementation produces a 24 MiB/s throughput, while
the go one only 2 MiB/s.
Note that the 'cat' operation (that writes device content to stdout)
is reasonably fast with both language implementaitions (doing around
70 MiB/s).
Why is this, how the Go binding could be improved?
TBH I've no idea. The bindings are meant to be very thin wrappers
around the C API, so I can't imagine that they should cause
performance penalties as large as you have observed.
Can you enable tracing in both (g.set_trace (true)) and make sure that
the operations being done are the same for both languages?
Rich.
Regards,
Csaba
<code lang="ruby">
#!/usr/bin/env ruby
require 'optparse'
require 'ostruct'
require 'guestfs'
module BaseOp
extend self
def included mod
mod.module_eval { extend self }
end
def perform gu, opts, gudev
end
attr_reader :readonly
end
module OPS
extend self
def find name
m = self.constants.find { |c| c.to_s.downcase == name }
m ? const_get(m) : nil
end
def names
constants.map &:downcase
end
module Cat
include BaseOp
@readonly = 1
def perform gu, opts
off = opts.offset
while true
buf = gu.pread_device opts.dev, opts.bs, off
break if buf.empty?
print buf
off += buf.size
end
end
end
module Write
include BaseOp
@readonly = 0
def perform gu, opts
off = opts.offset
while true
buf = STDIN.read opts.bs
break if (buf||"").empty?
siz = gu.pwrite_device opts.dev, buf, off
if siz != buf.size
raise "short write at offset #{off} (wanted #{buf.size}, done
#{siz})"
end
off += buf.size
end
end
end
module Ls
include BaseOp
@readonly = 1
def perform gu, opts
puts gu.list_devices
end
end
end
def main
opts = OpenStruct.new offset: 0, bs: 1<<20
optp = OptionParser.new
optp.banner << " [op] [diskimage]
op = [#{OPS.names.join ?|}]
options:"
optp.on("-d", "--guestdevice DEV", "guest device") { |c|
opts.dev = c }
optp.on("--blocksize BS", Integer,
"blocksize [default: #{opts.bs}]") { |n| opts.bs = n }
optp.on("-o OFF", "--offset", Integer,
"offset [default: #{opts.offset}]") { |n| opts.offset = n }
optp.parse!
unless $*.size == 2
STDERR.puts optp
exit 1
end
opname,image = $*
op = OPS.find opname
op or raise "unkown op #{opname} (should be one of #{OPS.names.join ?,})"
gu=Guestfs::Guestfs.new
begin
gu.add_drive_opts image, readonly: op.readonly
gu.launch
op.perform gu, opts
gu.shutdown
ensure
gu.close
end
end
if __FILE__ == $0
main
end
</code>
<code lang="go">
package main
import (
"flag"
"fmt"
"libguestfs.org/guestfs"
"log"
"os"
"path/filepath"
"strings"
)
type Op int
const (
OpUndef Op = iota
OpList
OpCat
OpWrite
)
var OpNames = map[Op]string{
OpList: "ls",
OpCat: "cat",
OpWrite: "write",
}
const usage = `%s [options] [op] [diskimage]
op = [%s]
options:
`
func main() {
var devname string
var bs int
var offset int64
log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.Usage = func() {
var ops []string
for _, on := range OpNames {
ops = append(ops, on)
}
fmt.Fprintf(flag.CommandLine.Output(), usage,
filepath.Base(os.Args[0]), strings.Join(ops, "|"))
flag.PrintDefaults()
}
flag.StringVar(&devname, "guestdevice", "", "guestfs
device name")
flag.IntVar(&bs, "blocksize", 1<<20, "blocksize")
flag.Int64Var(&offset, "offset", 0, "offset")
flag.Parse()
var opname string
var disk string
switch flag.NArg() {
case 2:
opname = flag.Arg(0)
disk = flag.Arg(1)
default:
flag.Usage()
os.Exit(1)
}
op := OpUndef
for o, on := range OpNames {
if opname == on {
op = o
break
}
}
if op == OpUndef {
log.Fatalf("unkown op %s\n", opname)
}
g, err := guestfs.Create()
if err != nil {
log.Fatalf("could not create guestfs handle: %s\n", err)
}
defer g.Close()
/* Attach the disk image to libguestfs. */
isReadonly := map[Op]bool{
OpList: true,
OpCat: true,
OpWrite: false,
}
optargs := guestfs.OptargsAdd_drive{
Readonly_is_set: true,
Readonly: isReadonly[op],
}
if err := g.Add_drive(disk, &optargs); err != nil {
log.Fatal(err)
}
/* Run the libguestfs back-end. */
if err := g.Launch(); err != nil {
log.Fatal(err)
}
switch op {
case OpList:
devices, err := g.List_devices()
if err != nil {
log.Fatal(err)
}
for _, dev := range devices {
fmt.Println(dev)
}
case OpCat:
for {
buf, err := g.Pread_device(devname, bs, offset)
if err != nil {
log.Fatal(err)
}
if len(buf) == 0 {
break
}
n, err1 := os.Stdout.Write(buf)
if err1 != nil {
log.Fatal(err1)
}
if n != len(buf) {
log.Fatal("stdout: short write")
}
offset += int64(len(buf))
}
case OpWrite:
buf := make([]byte, bs)
for {
n, err := os.Stdin.Read(buf)
if err != nil {
log.Fatal(err)
}
if n == 0 {
break
}
nw, err1 := g.Pwrite_device(devname, buf[:n], offset)
if err1 != nil {
log.Fatal(err1)
}
if nw != n {
log.Fatalf("short write at offset %d", offset)
}
offset += int64(n)
}
default:
panic("unknown op")
}
if err := g.Shutdown(); err != nil {
log.Fatal(err)
}
}
</code>
_______________________________________________
Libguestfs mailing list
Libguestfs(a)redhat.com
https://www.redhat.com/mailman/listinfo/libguestfs
virt-df lists disk usage of guests without needing to install any
software inside the virtual machine. Supports Linux and Windows.