#!/usr/sbin/dtrace -s
 /*
  * weblatency.d - website latency statistics.
  *		  Written using DTrace (Solaris 10 3/05).
  *
  * $Id: weblatency.d 3 2007-08-01 10:50:08Z brendan $
  *
  * USAGE:	weblatency.d 	# hit Ctrl-C to end sample
  *
  * See the code below for the "BROWSER" variable, which sets the browser
  * to trace (currently set to "mozilla-bin").
  *
  * This is written as an experimental tool, and may not work at all with
  * your browser.
  *
  * FIELDS:
  *		HOST		Hostname from URL
  *		NUM		Number of GETs
  *		AVGTIME(ms)	Average time for response, ms
  *		MAXTIME(ms)	Maximum time for response, ms
  *
  * NOTE:
  *
  * The latency measured here is from the browser sending the GET
  * request to when the browser begins to recieve the response. It
  * is an overall response time for the client, and encompasses
  * connection speed delays, DNS lookups, proxy delays, and web server
  * response time.
  *
  * IDEA: Bryan Cantrill (who wrote an elegant version for Sol 10 update 1)
  *
  * COPYRIGHT: Copyright (c) 2005, 2006 Brendan Gregg.
  *
  * CDDL HEADER START
  *
  *  The contents of this file are subject to the terms of the
  *  Common Development and Distribution License, Version 1.0 only
  *  (the "License").  You may not use this file except in compliance
  *  with the License.
  *
  *  You can obtain a copy of the license at Docs/cddl1.txt
  *  or http://www.opensolaris.org/os/licensing.
  *  See the License for the specific language governing permissions
  *  and limitations under the License.
  *
  * CDDL HEADER END
  *
  * ToDo:
  *	Check write fd for socket, not file.
  *
  * 30-Nov-2005  Brendan Gregg   Created this.
  * 20-Apr-2006	   "	  "	Last update.
  */
 
 #pragma D option quiet
 
 /* browser's execname */
 inline string BROWSER = "mozilla-bin";
 
 /* maximum expected hostname length + "GET http://" */
 inline int MAX_REQ = 64;
 
 dtrace:::BEGIN
 {
 	printf("Tracing... Hit Ctrl-C to end.\n");
 }
 
 /*
  * Trace brower request
  *
  * This is achieved by matching writes for the browser's execname that
  * start with "GET", and then timing from the return of the write to
  * the return of the next read in the same thread. Various stateful flags
  * are used: self->fd, self->read.
  *
  * For performance reasons, I'd like to only process writes that follow a
  * connect(), however this approach fails to process keepalives.
  */
 syscall::write:entry
 /execname == BROWSER/
 {
 	self->buf = arg1;
 	self->fd = arg0 + 1;
 	self->nam = "";
 }
 
 syscall::write:return
 /self->fd/
 {
 	this->str = (char *)copyin(self->buf, MAX_REQ);
 	this->str[4] = '\0';
 	self->fd = stringof(this->str) == "GET " ? self->fd : 0;
 }
 
 syscall::write:return
 /self->fd/
 {
 	/* fetch browser request */
 	this->str = (char *)copyin(self->buf, MAX_REQ);
 	this->str[MAX_REQ] = '\0';
 
 	/*
 	 * This unrolled loop strips down a URL to it's hostname.
 	 * We ought to use strtok(), but it's not available on Sol 10 3/05,
 	 * so instead I used dirname(). It's not pretty - it's done so that
 	 * this works on all Sol 10 versions.
 	 */
 	self->req = stringof(this->str);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->req = dirname(self->req);
 	self->nam = strlen(self->req) > 15 ? self->req : self->nam;
 	self->nam = basename(self->nam);
 
 	/* start the timer */
 	start[pid, self->fd - 1] = timestamp;
 	host[pid, self->fd - 1] = self->nam;
 	self->buf = 0;
 	self->fd  = 0;
 	self->req = 0;
 	self->nam = 0;
 }
 
 /* this one wasn't a GET */
 syscall::write:return
 /self->buf/
 {
 	self->buf = 0;
 	self->fd  = 0;
 }
 
 syscall::read:entry
 /execname == BROWSER && start[pid, arg0]/
 {
 	self->fd = arg0 + 1;
 }
 
 /*
  * Record host details
  */
 syscall::read:return
 /self->fd/
 {
 	/* fetch details */
 	self->host = stringof(host[pid, self->fd - 1]);
 	this->start = start[pid, self->fd - 1];
 
 	/* save details */
 	@Avg[self->host] = avg((timestamp - this->start)/1000000);
 	@Max[self->host] = max((timestamp - this->start)/1000000);
 	@Num[self->host] = count();
 
 	/* clear vars */
 	start[pid, self->fd - 1] = 0;
 	host[pid, self->fd - 1] = 0;
 	self->host = 0;
 	self->fd = 0;
 }
 
 /*
  * Output report
  */
 dtrace:::END
 {
 	printf("%-32s %11s\n", "HOST", "NUM");
 	printa("%-32s %@11d\n", @Num);
 
 	printf("\n%-32s %11s\n", "HOST", "AVGTIME(ms)");
 	printa("%-32s %@11d\n", @Avg);
 
 	printf("\n%-32s %11s\n", "HOST", "MAXTIME(ms)");
 	printa("%-32s %@11d\n", @Max);
 }