Go C10k

Inspired by (admittedly „ancient“) part 1 of a very instructive post series by Richard Jones and while waiting for Channels API support in Google’s new Go App Engine, I decided to make a quick port of Richard’s mochiconntest_web Erlang module to Go.

Source first, discussion later:

package main

import (
	"fmt"
	"http"
	"log"
	"time"
)

func main() {
	http.HandleFunc("/test/", feed)
	if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil {
		log.Fatal(err)
	}
}

func feed(w http.ResponseWriter, r *http.Request) {
	ch := time.Tick(1000000000)
	fmt.Fprintf(w, "Goconntest welcomes you! Your Id: %s\n", r.URL.String())
	w.(http.Flusher).Flush()
	for N := 1; ; N++ {
		<-ch
		n, err := fmt.Fprintf(w, "Chunk %d for id %s\n", N, r.URL.String())
		if err != nil {
			log.Print(err)
			break
		} else if n == 0 {
			log.Print("Nothing written")
			break
		}
		w.(http.Flusher).Flush()
	}
}

Of course this could be stripped down by ignoring (i.e. not handling) errors, but IMHO this doesn’t fall behind the Erlang implementation in terms of beauty; well, my sense of „beauty“ anyway.

You might have noted, that I’m not using the time.Sleep function, but instead a seemingly „artificial“ construct with a time.Ticker channel. This internally allows for a multiplexing of the Goroutines spawned by the http module to only a few system threads, scheduling the network operations via epoll.

Update 2011-05-23: Removed usage of ChunkedWriter from Go source according to a hint from Brad Fitzpatrick.

Update 2011-05-25: The flushing by w.(http.Flusher).Flush() is necessary to make sure that the chunks get written in-time. Thanks to Brad Fitzpatrick for pointing this out.

For the client side, I used Richard’s floodtest.erl module, only removing the {version, 1.1} line from the http:request, because that option doesn’t seem to be supported by Erlang R14B.

The result from my Intel(R) Core(TM) i7 CPU M 620 @ 2.67GHz:

The RSS usage of goconntest_web converges at 321MB i.e. roughly 32KB per connection (13KB less than the ad-hoc Erlang implementation). Also note, that Go indeed spawned only 4 threads on this quad-core machine to handle the 10k open connections.

The only disappointment was the rather high CPU usage. Although I’m aware that the screen-shot is far from any acceptable benchmark measure, still 1.5h CPU time seems to be more than „practically nothing“ Richard reported for the Erlang implementation.

Update 2011-05-25: While testing the new code above, I logged the process statistics with pidstat and then made the following plot (Python script, data file):

Mind the logarithmic time scale! The dashed line indicates the ramp-up of opened connections to 10k (theoretical, not measured). The colorful background is a stacked plot of the per-thread CPU usage with one color per kernel thread-id. Obviously Go spawns more threads than there are cores. Finally, the thick black line is the resident memory usage, which seems to converge much later than the CPU usage.

Thanks for reading. Maybe, if the App Engine Channels API takes long enough, I will continue my investigations.

xsession-errors

Ever since I upgraded my notebook with a SSD, I was looking for a way to minimise or avoid unnecessary write accesses. The tools of choice (on Linux at least) are iostat for a rough summary and pidstat for the details. With the help of those two one can easily figure out which processes are responsible for write accesses.

Once a writing process is identified, lsof can tell you which files are actually written. Depending on what you want to achieve, you can then continue to relocate that file to a different disk (e.g. if you have two disks and want one of them to sleep most of the time) or to memory (using tmpfs). The latter obviously means that the written data will be lost at the next reboot, but sometimes this is perfectly fine e.g. for the files inside /tmp. Therefore, it is generally a good idea to move that directory into volatile memory following the instructions on the openSUSE Wiki.

All good and well if you actually manage to relocate or move a file. Often file locations are a matter of configuration and otherwise you can help yourself with dynamic links. But there was one very special file on my openSUSE 11.4 installation that withstood all my assaults for quiet some time. I’m speaking of a beast called .xsession-errors residing in your $HOME directory. Created by KDM, one would expect to be able to configure the location of that file. Indeed, there is a configuration option called ClientLogFile specifically for that purpose. Unfortunately this is only the first and easier of two necessary steps:

  1. As root open your /usr/share/kde4/config/kdm/kdmrc, go to a section labelled [X-:0-Core] (there may be multiple of those, but don’t worry and just pick the last one) and add the following line:
    ClientLogFile=../../tmp/xsession-errors-%u

    This will move the file into the /tmp directory. Mind the path relative to $HOME.

  2. Now the hidden piece: again as root open your /etc/X11/xdm/Xsession and make the following changes (I deliberately use the patch syntax here i.e. remove lines with a minus sign and add those with a plus sign):
    --- Xsession.old        2011-05-01 19:46:40.000000000 +0200
    +++ Xsession    2011-05-02 22:18:39.000000000 +0200
    @@ -123,8 +123,8 @@
         # GDM seems to handle this its self
         test -z "$GDMSESSION" || break
    
    -    # Once if KDM does handle this its self
    -    #test -z "$KDMSESSION" || break
    +    # KDM handles this itself
    +    test -z "$KDE_SESSION_VERSION" || break
    
         # Avoid bad symbolic links
         case "$errfile" in

Done; logout, restart KDM, re-login and check if the xsession-errors* exists at the new location. If so, remove your old one and cheer to a long living SSD. Only, of course, if the new location is not on your SSD, but e.g. in memory. It doesn’t hurt to re-check with pidstat.