CSS 390: Notes from Lecture 14 (DRAFT)

Decorators (Wrappers)

Example:


package main

import (
	"fmt"
)

// Your basic Fibonacci function:
func Fib(x int) int {
	prev, sum := 1, 1
	for i := 1; i < x; i++ {
		// Taking advantage of parallel assignment:
		prev, sum = sum, sum+prev
	}
	return sum
}

// Function that returns a function:
// Note the "unbound" variable m which is defined in the
// outer scope.  Each time Multiplier is called, a new
// of the inner function is created that is associated with the outer
// environment (lexical closure).
func Multiplier(m int) func(int) int {
	return func(x int) int { return x * m }
}

func main() {
	// Function Fib is assigned to (and called via) the variable
	// Fib.
	// Observe that the expression assigns the function, instead
	// of calling the function.
	fib := Fib

	// Two instances of the function returned by Multiplier:
	double := Multiplier(2)  // double multiplies by 2
	triple := Multiplier(3)  // triple multiplies by 3

        // Loop calling three functions via variables
	for i := 0; i < 20; i++ {
		fmt.Printf("%d\t%d\t%d\t%d\n", i, fib(i), double(i), triple(i))
	}
}

For contrast, an alternate, clunkier way to accomplish the same thing is to use an object:


package main

import (
	"fmt"
)

// An Evaluator is any type that has a matching Eval method.
type Evaluator interface {
	Eval(x int) int
}

// Fib is an empty struct because it's just a type that doesn't need
// any data members.
type Fib struct{}

// Same Fibonacci function
func (f Fib) Eval(x int) int {
	prev, sum := 1, 1
	for i := 1; i < x; i++ {
		prev, sum = sum, sum+prev
	}
	return sum
}

// multiple takes the roll of the closure variable.
type Multiplier struct {
	multiple int
}

// Implement the eval method for type Multiplier
func (m Multiplier) Eval(x int) int {
	return m.multiple * x
}

func main() {
	// Create some object instances:
	double := Multiplier{2}
	triple := Multiplier{3}
	fib := Fib{}

	// Slice of objects.
	funcs := []Evaluator{double, triple, fib}

	for i := 0; i < 20; i++ {
		fmt.Printf("%d", i)
		// For each object, call its Eval method..
		for _, f := range funcs {
			fmt.Printf("\t%d", f.Eval(i))
		}
		fmt.Printf("\n")
	}
}

// Alternate main function binding the method to the object.
func altmain() {
	double := Multiplier{2}.Eval
	triple := Multiplier{3}.Eval
	fib := Fib{}.Eval

	// Slice of functions
	funcs := []Evaluator{double, triple, fib}

	for i := 0; i < 20; i++ {
		fmt.Printf("%d", i)
		for _, f := range funcs {
			fmt.Printf("\t%d", f(i))
		}
		fmt.Printf("\n")
	}
}

If we work directly with bound methods, Go does not actually require the interface declaration, since the type of the variables is function that takes int and returns int and the type of the underlying object falls out.


package main

import (
	"fmt"
)

type Datum struct {
	Multiplier int
}

func (d Datum) Eval(x int) int {
	return d.Multiplier * x
}

type Fib struct{}

func (f Fib) Eval(x int) int {
	prev, sum := 1, 1
	for i := 1; i < x; i++ {
		prev, sum = sum, sum+prev
	}
	return sum
}

func main() {
	double := Datum{2}.Eval
	triple := Datum{3}.Eval
	fib := Fib{}.Eval
	for i := 0; i < 20; i++ {
		fmt.Printf("%d\t%d\t%d\t%d\n", i, double(i), triple(i), fib(i))
	}
}

Decorators (Wrappers)

Assignment 4 requires adding a counter to the handler function. A similar problem is to calculate service times.

In this case, the business logic ("Zeus was here."/"Jupiter was here.") is swamped by the bookkeeping. Note the repetition: it's a code smell


package main

import (
	"fmt"
	"net/http"
	"time"
)

func handleZeus(wr http.ResponseWriter, req *http.Request) {
	start := time.Now()
	fmt.Printf("%s\tbegin: %s\n", start.Format(time.RFC3339Nano), req.RequestURI)

	wr.Header().Add("content-type", "text/plain")
	fmt.Fprintf(wr, "Zeus was here.")

	end := time.Now()
	fmt.Printf("%s\tend(%s elapsed): %s\n", end.Format(time.RFC3339Nano), end.Sub(start), req.RequestURI)
}

func handleJupiter(wr http.ResponseWriter, req *http.Request) {
	start := time.Now()
	fmt.Printf("%s\tbegin: %s\n", start.Format(time.RFC3339Nano), req.RequestURI)

	wr.Header().Add("content-type", "text/plain")
	fmt.Fprintf(wr, "Jupiter was here")

	end := time.Now()
	fmt.Printf("%s\tend(%s elapsed): %s\n", end.Format(time.RFC3339Nano), end.Sub(start), req.RequestURI)
}

func main() {
	http.HandleFunc("/zeus", handleZeus)
	http.HandleFunc("/jupiter", handleJupiter)
	err := http.ListenAndServe(":8090", nil)
	fmt.Printf("server: %s\n", err)
}

So we can fix it easily using a closure to decorate or wrap the handler function


package main

import (
	"fmt"
	"net/http"
	"time"
)

// Wrapper function takes a handler type function and returns a
// handler type function.
// Note that we could declare a type, say
//    type HF func(http.ResponseWriter, *http.Request)
// and simplify the declaration of LogRequest:
//    func LogRequest(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
//
// Also pointed out in class, the http package already declares the
// type HandlerFunction that does exactly this.
func LogRequest(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	// Function literal with handler as an unbound variable: this
	// forms a yet another function closure:
	return func(wr http.ResponseWriter, req *http.Request) {
		start := time.Now()
		fmt.Printf("%s\tbegin: %s\n", start.Format(time.RFC3339Nano), req.RequestURI)
		handler(wr, req)
		end := time.Now()
		fmt.Printf("%s\tend(%s elapsed): %s\n", end.Format(time.RFC3339Nano), end.Sub(start), req.RequestURI)
	}
}

// Handler functions just need to have the business logic.

func handleZeus(wr http.ResponseWriter, req *http.Request) {
	wr.Header().Add("content-type", "text/plain")
	fmt.Fprintf(wr, "Zeus was here.")
}

func handleJupiter(wr http.ResponseWriter, req *http.Request) {
	wr.Header().Add("content-type", "text/plain")
	fmt.Fprintf(wr, "Jupiter was here")
}

func main() {
	http.HandleFunc("/zeus", LogRequest(handleZeus))
	http.HandleFunc("/jupiter", LogRequest(handleJupiter))
	err := http.ListenAndServe(":8090", nil)
	fmt.Printf("server: %s\n", err)
}

Note that we can stack the wrappers:


package main

import (
	"fmt"
	"net/http"
	"time"
)

func LogRequest(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	return func(wr http.ResponseWriter, req *http.Request) {
		start := time.Now()
		fmt.Printf("%s\tbegin: %s\n", start.Format(time.RFC3339Nano), req.RequestURI)
		handler(wr, req)
		end := time.Now()
		fmt.Printf("%s\tend(%s elapsed): %s\n", end.Format(time.RFC3339Nano), end.Sub(start), req.RequestURI)
	}
}

func PlainText(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	return func(wr http.ResponseWriter, req *http.Request) {
		wr.Header().Add("content-type", "text/plain")
		handler(wr, req)
	}
}

// This is a function that returns a function, but it is not (by
// itself) a closure since there are no unbound variables in this
// lexical scope.
//
// Much more complex, but this complexity gets hidden (abstracted)
// away from the business logic.
func UberWrap(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	return LogRequest(PlainText(handler)
}

// Handler functions just have the business logic: much simpler

func handleZeus(wr http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(wr, "Zeus was here.")
}

func handleJupiter(wr http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(wr, "Jupiter was here")
}

func main() {
	http.HandleFunc("/zeus", UberWrap(handleZeus))
	http.HandleFunc("/jupiter", UberWrap(handleJupiter))
	err := http.ListenAndServe(":8090", nil)
	fmt.Printf("server: %s\n", err)
}

Additional Notes:

For constrast, the same functionality may be implemented using objects:


package main

import (
	"fmt"
	"net/http"
	"time"
)

type Wrapper struct {
	// Data member is a function object
	body func(http.ResponseWriter, *http.Request)
}

func (wrap *Wrapper) Handle(wr http.ResponseWriter, req *http.Request) {
	start := time.Now()
	fmt.Printf("%s\tbegin: %s\n", start.Format(time.RFC3339Nano), req.RequestURI)
	wrap.body(wr, req)
	end := time.Now()
	fmt.Printf("%s\tend(%s elapsed): %s\n", end.Format(time.RFC3339Nano), end.Sub(start), req.RequestURI)
}

func handleZeus(wr http.ResponseWriter, req *http.Request) {
	wr.Header().Add("content-type", "text/plain")
	fmt.Fprintf(wr, "Zeus was here.")
}

func handleJupiter(wr http.ResponseWriter, req *http.Request) {
	wr.Header().Add("content-type", "text/plain")
	fmt.Fprintf(wr, "Jupiter was here")
}

func main() {
	//  Create wrapper objects.
	wrappedZeus := Wrapper{handleZeus}
	wrappedJupiter := Wrapper{handleJupiter}

	// Bind the Handle method of the Wrapper objects.
	handleWrappedZeus := wrappedZeus.Handle
	handleWrappedJupiter := wrappedJupiter.Handle

	// Set up the handlers.
	http.HandleFunc("/zeus", handleWrappedZeus)
	http.HandleFunc("/jupiter", handleWrappedJupiter)

	err := http.ListenAndServe(":8090", nil)
	fmt.Printf("server: %s\n", err)
}

// Alternate main: doing this thing in a single step.
func Altmain() {
	http.HandleFunc("/zeus", (&Wrapper{handleZeus}).Handle)
	http.HandleFunc("/jupiter", (&Wrapper{handleJupiter}).Handle)

	err := http.ListenAndServe(":8090", nil)
	fmt.Printf("server: %s\n", err)
}

Monitoring

Observe the system monitoring on your Mac or PC. Consider the same tool for monitoring a large-scale service. Production-grade monitoring tools (e.g. Munin) operate on the same principle, but on steroids. The monitor periodically collects samples of system parameters of the service machines (e.g. CPU load, disk activity, memory usage) plus parameters specific to each service (e.g. request count, number of 500s, latency).

Note that the monitoring software performs four functions:

  1. data collection
  2. analysis
  3. display
  4. alerting

Time Series

A time series is a sequence of data points, typically consisting of successive measurements made over a time interval. This is exactly what the monitor does.

Typically, the monitoring collects absolute counts and then analyzes the data to compute rates.

More Bash

While script files are, essentially, the same commands you can type at the command line, there are some subtleties in the deployment script below that will benefit from additional explanation.

Quoting

Single quotes are more quotey than double quotes, but the backslash escapes the special symbols inside double quotes.


morris@yogi:~/uw/css490/2015-q1/lectures/lecture-14$ GOD=Thor
morris@yogi:~/uw/css490/2015-q1/lectures/lecture-14$ echo "$GOD was here"
Thor was here
morris@yogi:~/uw/css490/2015-q1/lectures/lecture-14$ echo "\$GOD was here"
$GOD was here
morris@yogi:~/uw/css490/2015-q1/lectures/lecture-14$ echo '$GOD was here'
$GOD was here
morris@yogi:~/uw/css490/2015-q1/lectures/lecture-14$ echo '\$GOD was here'
\$GOD was here

A here document1 is like a multiline quoted string that is used as a replacement for standard input to a command (note the shell variable replacement):


morris@yogi:~/uw/css490/2015-q1$ cat -n <<END
> Some Norse Gods
> o  Odin
> o  $GOD
> o  Loki
> END
     1	Some Norse Gods
     2	o  Odin
     3	o  Thor
     4	o  Loki

Backquotes

Backquotes pass the quoted string to the shell as a command and replaces the quoted string with the command's output.


morrisb9@uw1-320-07:~$ echo "the host is `hostname -f`"
the host is uw1-320-07.uwb.edu

Sending a Here Doc to a Remote Shell

When we use a here document as input to a remote shell (ssh), we must escape the $ and ` characters so they will not be interpreted by the local shell. Instead, they are passed to the remote shell which does the interpretation.

The first try below fails because the local shell does not have a host variable (it is set in the remote shell). The second try fails because the backquotes are expanded by the local shell before the contents of the here document is passed to the remote shell. Third time lucky.


morris@yogi:~/uw/css490/2015-q1$ ssh morrisb9@uw1-320-lab.uwb.edu <<END
host=`hostname -f`
echo the host is $host
END
the host is
morris@yogi:~/uw/css490/2015-q1$ ssh morrisb9@uw1-320-lab.uwb.edu <<END
host=`hostname -f`
echo the host is \$host
END
the host is yogi
morris@yogi:~/uw/css490/2015-q1$ ssh morrisb9@uw1-320-lab.uwb.edu <<END
host=\`hostname -f\`
echo the host is \$host
END
the host is uw1-320-11.uwb.edu

Sed and Perl

sed and perl come in really handy for small pieces of string processing.

Deployment

There are several discrete steps involved in deploying software onto production systems:

At any of these stages something can go wrong, so it is important to check the status codes of each step.

Deployment Script

The following Bash script, while not especially robust2, demonstrates use of the shell as job control and covers essential elements of deployment of the timeserver system on the CSS Linux Lab machines.

Production Deployment Systems

As the simple script above demonstrates, there are a lot of elements to deployment where something can go wrong. Most of the script is about checking, which is essentially boilerplate, essentially common to all deployments.

Production-grade deployment systems such as Chef, Puppet, or Ansible take a relatively simple configuration file, typically in a standard format such as XML or YAML, that describes what files must be available and what processes must be run when.

Footnotes