Statistics
authorAdam Chlipala <adamc@hcoop.net>
Sat, 13 Aug 2005 16:26:18 +0000 (16:26 +0000)
committerAdam Chlipala <adamc@hcoop.net>
Sat, 13 Aug 2005 16:26:18 +0000 (16:26 +0000)
.cvsignore
mlt.conf
out/.cvsignore [new file with mode: 0644]
portal.mlt
quotas.mlt [new file with mode: 0644]
stats.sig [new file with mode: 0644]
stats.sml [new file with mode: 0644]
webbw.mlt [new file with mode: 0644]

index a2a33e4..bb80809 100644 (file)
@@ -1,2 +1,3 @@
 .cm
 CM
+out
\ No newline at end of file
index 4b27a02..f20b644 100644 (file)
--- a/mlt.conf
+++ b/mlt.conf
@@ -6,7 +6,8 @@ after   after
 exn    exn
 
 out    out
-pub    /var/www/users.hcoop.net/cgi/hcoop
+pub    /home/hcoop/public_html/cgi-bin/portal
 
+cm     $/smlnj-lib.cm
 cm     /usr/local/share/smlsql/smlsql.cm
 cm     /usr/local/share/smlsql/libpq/sources.cm
diff --git a/out/.cvsignore b/out/.cvsignore
new file mode 100644 (file)
index 0000000..f59ec20
--- /dev/null
@@ -0,0 +1 @@
+*
\ No newline at end of file
index 5839663..ff3a863 100644 (file)
@@ -55,6 +55,11 @@ end %>
 <br>
 <% end %>
 
+<h3><b>Statistics</b></h3>
+
+<a href="webbw">Apache bandwidth</a><br>
+<a href="quotas">Disk usage</a><br>
+
 <h3><b>Public pages</b></h3>
 
 <a href="http://hcoop.net/dyn/members.html">Member directory</a><br>
diff --git a/quotas.mlt b/quotas.mlt
new file mode 100644 (file)
index 0000000..9f6a04c
--- /dev/null
@@ -0,0 +1,22 @@
+<% @header [("title", ["Disk usage stats"])] %>
+
+<table>
+<tr> <td align="right"><b>User</b></td> <td><b>Blocks</b></td> <td><b>Files</b></td> </tr>
+<tr><td>&nbsp;</td></tr>
+
+<% foreach du in Stats.getDiskUsage () do %>
+       <tr><td align="right"><%
+       val id = Init.userNameToId (#uname du);
+       switch id of
+                 SOME id => %><a href="user?id=<% id %>"><%
+       end
+       %><% Web.html (#uname du) %><%
+       switch id of
+                 SOME id => %></a><%
+       end
+%></td> <td><% #blocks du %></td> <td><% #files du %></td> </tr>
+<% end %>
+
+</table>
+
+</body></html>
\ No newline at end of file
diff --git a/stats.sig b/stats.sig
new file mode 100644 (file)
index 0000000..8bf823c
--- /dev/null
+++ b/stats.sig
@@ -0,0 +1,19 @@
+signature STATS =
+sig
+    type host = {ssl : bool,        (* Is it HTTPS? *)
+                hostname : string, (* Internet hostname *)
+                id : string}       (* Name of stats directory *)
+
+    val getWebbw : int -> int * (host * int) list * (string * host list * int) list
+    (* Get web bandwidth usage stats.  The argument tells how many months ago
+     * to look for the data.  The return gives the total b/w usage, a mapping from
+     * vhosts to kilobytes, and a mapping from usernames to their vhosts and bandwidth
+     * totals. *)
+
+    type disk = {uname : string,    (* UNIX username *)
+                blocks : int,      (* Number of disk blocks used *)
+                files : int}       (* Number of files used *)
+
+    val getDiskUsage : unit -> disk list
+    (* Get /home disk usage statistics, in descending blocks order. *)
+end
diff --git a/stats.sml b/stats.sml
new file mode 100644 (file)
index 0000000..620b71e
--- /dev/null
+++ b/stats.sml
@@ -0,0 +1,115 @@
+structure Stats :> STATS =
+struct
+   val webbw = "/etc/stats/webbw"
+   val webbw_last = "/etc/stats/webbw.last"
+   val webbw_last2 = "/etc/stats/webbw.last2"
+
+   type host = {ssl : bool,
+               hostname : string,
+               id : string}
+
+   fun checkSsl host =
+       case String.fields (fn ch => ch = #".") host of
+          first::rest =>
+          (case String.fields (fn ch => ch = #"_") first of
+               [first, "ssl"] => {ssl = true, hostname = String.concatWith "." (first::rest),
+                                  id = host}
+             | _ => {ssl = false, hostname = host, id = host})
+        | _ => {ssl = false, hostname = host, id = host}
+
+   fun getWebbw last =
+       let
+          val fname =
+              case last of
+                  2 => webbw_last2
+                | 1 => webbw_last
+                | 0 => webbw
+                | _ => raise Fail "Asked for too old of a bandwidth file"
+
+          val inf = TextIO.openIn fname
+
+          val sum = case TextIO.inputLine inf of
+                        NONE => raise Fail "Can't read webbw"
+                      | SOME l =>
+
+                        case String.tokens Char.isSpace l of
+                            [_, n] => valOf (Int.fromString n)
+                          | _ => raise Fail "Bad total in webbw"
+
+          fun readEm L =
+              case TextIO.inputLine inf of
+                  (NONE | SOME "\n") => List.rev L
+                | SOME l =>
+                  case String.tokens (fn ch => Char.isSpace ch orelse ch = #":") l of
+                      [d, n] => readEm ((checkSsl d, valOf (Int.fromString n)) :: L)
+                    | _ => raise Fail "Bad row in webbw"
+
+          fun splitLast [] = raise Fail "Not enough items for splitLast"
+            | splitLast [x] = ([], x)
+            | splitLast (h::t) =
+              let
+                  val (l, x) = splitLast t
+              in
+                  (h::l, x)
+              end
+
+          fun readGroups L =
+              case TextIO.inputLine inf of
+                  NONE => List.rev L
+                | SOME l =>
+                  case String.tokens (fn ch => Char.isSpace ch orelse ch = #":" orelse ch = #"[" orelse ch = #"]" orelse ch = #",") l of
+                      d :: rest =>
+                      let
+                          val (l, x) = splitLast rest
+                      in
+                          readGroups ((d, map checkSsl l, valOf (Int.fromString x)) :: L)
+                      end
+                    | _ => raise Fail "Bad row in webbw, part 2"
+       in
+          TextIO.inputLine inf;
+          (sum, readEm [], readGroups [])
+          before TextIO.closeIn inf
+       end
+
+    type disk = {uname : string,
+                blocks : int,
+                files : int}
+
+    fun getDiskUsage () =
+       let
+           val proc = Unix.execute ("/usr/bin/sudo", ["/usr/sbin/repquota", "-g", "/home"])
+           val inf = Unix.textInstreamOf proc
+
+           fun skipUntilLine () =
+               case TextIO.inputLine inf of
+                   NONE => raise Fail "No dividing line found in repquota output"
+                 | SOME s =>
+                   if String.sub (s, 0) = #"-" then
+                       ()
+                   else
+                       skipUntilLine ()
+
+           fun readData acc =
+               let
+                   fun done () =
+                       ListMergeSort.sort (fn (d1, d2) =>
+                                              #blocks d1 < #blocks d2) acc
+               in
+                   case TextIO.inputLine inf of
+                       NONE => done ()
+                     | SOME s =>
+                       case String.tokens Char.isSpace s of
+                           [uname, dash, blocks, bsoft, bhard, files, fsoft, fhard] =>
+                           readData ({uname = uname,
+                                      blocks = valOf (Int.fromString blocks),
+                                      files = valOf (Int.fromString files)} :: acc)
+                         | [] => done ()
+                         | _ => raise Fail ("Bad repquota line: " ^ s)
+               end
+       in
+           skipUntilLine ();
+           readData []
+           before ignore (Unix.reap proc)
+       end
+
+end
diff --git a/webbw.mlt b/webbw.mlt
new file mode 100644 (file)
index 0000000..95a8591
--- /dev/null
+++ b/webbw.mlt
@@ -0,0 +1,47 @@
+<% @header [("title", ["Web usage stats"])] %>
+
+<b><u>Web virtual host bandwidth</u></b> (bandwidth usage of Apache sites in this calendar month)<br>
+<% if $"last" = "1" then %>
+       <a href="webbw?last=2">[Previous]</a> | <a href="webbw?last=0">[Current]</a>
+<% elseif $"last" = "2" then %>
+       <a href="webbw?last=1">[Next]</a>
+<% else %>
+       <a href="webbw?last=1">[Last month]</a>
+<% end %>
+<br><br>
+
+<% val (sum, doms, groups) = iff $"last" = "" then
+                       Stats.getWebbw 0
+               else    
+                       Stats.getWebbw (Web.stoi ($"last")) %>
+
+<table>
+<tr><td></td><td><b>Bandwidth (kB)</b></td></tr>
+<tr><td align="right"><b>Total Apache bandwidth</b></td> <td><% sum %></td></tr>
+<tr><td>&nbsp;</td></tr>
+
+<% foreach (d, n) in doms do %>
+       <tr><td align="right"><a href="<% if #ssl d then %>https<% else %>http<% end %>://<% #hostname d %>/"><% #hostname d %></a> <a href="/webalizer/<% #id d %>/">[detail]</a></td><td><% n %></td></tr>
+<% end %>
+
+</table>
+
+<br><br>
+<b><u>Bandwidth by ownership</u></b>
+<br><br>
+
+<table>
+<tr><td></td><td></td><td><b>Bandwidth (kB)</b></td></tr>
+<tr><td>&nbsp;</td></tr>
+
+<% foreach (gr, ds, n) in groups do %>
+       <tr><td align="right"><% gr %></td><td>
+       <% foreach dom in ds do %>
+               [<a href="/webalizer/<% #id dom %>/"><% #hostname dom %></a> <a href="<% if #ssl dom then %>https<% else %>http<% end %>://<% #hostname dom %>/">(S)</a>]
+       <% end %>
+       </td><td><% n %></td></tr>
+<% end %>
+
+</table>
+
+</body></html>
\ No newline at end of file