You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by ct...@apache.org on 2013/11/01 01:56:08 UTC

[29/54] [partial] ACCUMULO-658, ACCUMULO-656 Split server into separate modules

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/docs/examples/README.terasort
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/docs/examples/README.terasort b/server/monitor/src/main/resources/docs/examples/README.terasort
new file mode 100644
index 0000000..cf5051a
--- /dev/null
+++ b/server/monitor/src/main/resources/docs/examples/README.terasort
@@ -0,0 +1,50 @@
+Title: Apache Accumulo Terasort Example
+Notice:    Licensed to the Apache Software Foundation (ASF) under one
+           or more contributor license agreements.  See the NOTICE file
+           distributed with this work for additional information
+           regarding copyright ownership.  The ASF licenses this file
+           to you under the Apache License, Version 2.0 (the
+           "License"); you may not use this file except in compliance
+           with the License.  You may obtain a copy of the License at
+           .
+             http://www.apache.org/licenses/LICENSE-2.0
+           .
+           Unless required by applicable law or agreed to in writing,
+           software distributed under the License is distributed on an
+           "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+           KIND, either express or implied.  See the License for the
+           specific language governing permissions and limitations
+           under the License.
+
+This example uses map/reduce to generate random input data that will
+be sorted by storing it into accumulo.  It uses data very similar to the
+hadoop terasort benchmark.
+
+To run this example you run it with arguments describing the amount of data:
+
+    $ bin/tool.sh lib/accumulo-examples-simple.jar org.apache.accumulo.examples.simple.mapreduce.TeraSortIngest \
+    -i instance -z zookeepers -u user -p password \
+    --count 10 \
+    --minKeySize 10 \ 
+    --maxKeySize 10 \
+    --minValueSize 78 \
+    --maxValueSize 78 \
+    --table sort \
+    --splits 10 \
+
+After the map reduce job completes, scan the data:
+
+    $ ./bin/accumulo shell -u username -p password
+    username@instance> scan -t sort 
+    +l-$$OE/ZH c:         4 []    GGGGGGGGGGWWWWWWWWWWMMMMMMMMMMCCCCCCCCCCSSSSSSSSSSIIIIIIIIIIYYYYYYYYYYOOOOOOOO
+    ,C)wDw//u= c:        10 []    CCCCCCCCCCSSSSSSSSSSIIIIIIIIIIYYYYYYYYYYOOOOOOOOOOEEEEEEEEEEUUUUUUUUUUKKKKKKKK
+    75@~?'WdUF c:         1 []    IIIIIIIIIIYYYYYYYYYYOOOOOOOOOOEEEEEEEEEEUUUUUUUUUUKKKKKKKKKKAAAAAAAAAAQQQQQQQQ
+    ;L+!2rT~hd c:         8 []    MMMMMMMMMMCCCCCCCCCCSSSSSSSSSSIIIIIIIIIIYYYYYYYYYYOOOOOOOOOOEEEEEEEEEEUUUUUUUU
+    LsS8)|.ZLD c:         5 []    OOOOOOOOOOEEEEEEEEEEUUUUUUUUUUKKKKKKKKKKAAAAAAAAAAQQQQQQQQQQGGGGGGGGGGWWWWWWWW
+    M^*dDE;6^< c:         9 []    UUUUUUUUUUKKKKKKKKKKAAAAAAAAAAQQQQQQQQQQGGGGGGGGGGWWWWWWWWWWMMMMMMMMMMCCCCCCCC
+    ^Eu)<n#kdP c:         3 []    YYYYYYYYYYOOOOOOOOOOEEEEEEEEEEUUUUUUUUUUKKKKKKKKKKAAAAAAAAAAQQQQQQQQQQGGGGGGGG
+    le5awB.$sm c:         6 []    WWWWWWWWWWMMMMMMMMMMCCCCCCCCCCSSSSSSSSSSIIIIIIIIIIYYYYYYYYYYOOOOOOOOOOEEEEEEEE
+    q__[fwhKFg c:         7 []    EEEEEEEEEEUUUUUUUUUUKKKKKKKKKKAAAAAAAAAAQQQQQQQQQQGGGGGGGGGGWWWWWWWWWWMMMMMMMM
+    w[o||:N&H, c:         2 []    QQQQQQQQQQGGGGGGGGGGWWWWWWWWWWMMMMMMMMMMCCCCCCCCCCSSSSSSSSSSIIIIIIIIIIYYYYYYYY
+
+Of course, a real benchmark would ingest millions of entries.

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/docs/examples/README.visibility
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/docs/examples/README.visibility b/server/monitor/src/main/resources/docs/examples/README.visibility
new file mode 100644
index 0000000..7ddbf1d
--- /dev/null
+++ b/server/monitor/src/main/resources/docs/examples/README.visibility
@@ -0,0 +1,131 @@
+Title: Apache Accumulo Visibility, Authorizations, and Permissions Example
+Notice:    Licensed to the Apache Software Foundation (ASF) under one
+           or more contributor license agreements.  See the NOTICE file
+           distributed with this work for additional information
+           regarding copyright ownership.  The ASF licenses this file
+           to you under the Apache License, Version 2.0 (the
+           "License"); you may not use this file except in compliance
+           with the License.  You may obtain a copy of the License at
+           .
+             http://www.apache.org/licenses/LICENSE-2.0
+           .
+           Unless required by applicable law or agreed to in writing,
+           software distributed under the License is distributed on an
+           "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+           KIND, either express or implied.  See the License for the
+           specific language governing permissions and limitations
+           under the License.
+
+## Creating a new user
+
+    root@instance> createuser username
+    Enter new password for 'username': ********
+    Please confirm new password for 'username': ********
+    root@instance> user username
+    Enter password for user username: ********
+    username@instance> createtable vistest
+    06 10:48:47,931 [shell.Shell] ERROR: org.apache.accumulo.core.client.AccumuloSecurityException: Error PERMISSION_DENIED - User does not have permission to perform this action
+    username@instance> userpermissions
+    System permissions: 
+    
+    Table permissions (!METADATA): Table.READ
+    username@instance> 
+
+A user does not by default have permission to create a table.
+
+## Granting permissions to a user
+
+    username@instance> user root
+    Enter password for user root: ********
+    root@instance> grant -s System.CREATE_TABLE -u username
+    root@instance> user username 
+    Enter password for user username: ********
+    username@instance> createtable vistest
+    username@instance> userpermissions
+    System permissions: System.CREATE_TABLE
+    
+    Table permissions (!METADATA): Table.READ
+    Table permissions (vistest): Table.READ, Table.WRITE, Table.BULK_IMPORT, Table.ALTER_TABLE, Table.GRANT, Table.DROP_TABLE
+    username@instance vistest> 
+
+## Inserting data with visibilities
+
+Visibilities are boolean AND (&) and OR (|) combinations of authorization
+tokens.  Authorization tokens are arbitrary strings taken from a restricted 
+ASCII character set.  Parentheses are required to specify order of operations 
+in visibilities.
+
+    username@instance vistest> insert row f1 q1 v1 -l A
+    username@instance vistest> insert row f2 q2 v2 -l A&B
+    username@instance vistest> insert row f3 q3 v3 -l apple&carrot|broccoli|spinach
+    06 11:19:01,432 [shell.Shell] ERROR: org.apache.accumulo.core.util.BadArgumentException: cannot mix | and & near index 12
+    apple&carrot|broccoli|spinach
+                ^
+    username@instance vistest> insert row f3 q3 v3 -l (apple&carrot)|broccoli|spinach
+    username@instance vistest> 
+
+## Scanning with authorizations
+
+Authorizations are sets of authorization tokens.  Each Accumulo user has 
+authorizations and each Accumulo scan has authorizations.  Scan authorizations 
+are only allowed to be a subset of the user's authorizations.  By default, a 
+user's authorizations set is empty.
+
+    username@instance vistest> scan
+    username@instance vistest> scan -s A
+    06 11:43:14,951 [shell.Shell] ERROR: java.lang.RuntimeException: org.apache.accumulo.core.client.AccumuloSecurityException: Error BAD_AUTHORIZATIONS - The user does not have the specified authorizations assigned
+    username@instance vistest> 
+
+## Setting authorizations for a user
+
+    username@instance vistest> setauths -s A
+    06 11:53:42,056 [shell.Shell] ERROR: org.apache.accumulo.core.client.AccumuloSecurityException: Error PERMISSION_DENIED - User does not have permission to perform this action
+    username@instance vistest> 
+
+A user cannot set authorizations unless the user has the System.ALTER_USER permission.
+The root user has this permission.
+
+    username@instance vistest> user root
+    Enter password for user root: ********
+    root@instance vistest> setauths -s A -u username
+    root@instance vistest> user username
+    Enter password for user username: ********
+    username@instance vistest> scan -s A
+    row f1:q1 [A]    v1
+    username@instance vistest> scan
+    row f1:q1 [A]    v1
+    username@instance vistest> 
+
+The default authorizations for a scan are the user's entire set of authorizations.
+
+    username@instance vistest> user root
+    Enter password for user root: ********
+    root@instance vistest> setauths -s A,B,broccoli -u username
+    root@instance vistest> user username
+    Enter password for user username: ********
+    username@instance vistest> scan
+    row f1:q1 [A]    v1
+    row f2:q2 [A&B]    v2
+    row f3:q3 [(apple&carrot)|broccoli|spinach]    v3
+    username@instance vistest> scan -s B
+    username@instance vistest> 
+    
+If you want, you can limit a user to only be able to insert data which they can read themselves.
+It can be set with the following constraint.
+
+    username@instance vistest> user root
+    Enter password for user root: ******
+    root@instance vistest> config -t vistest -s table.constraint.1=org.apache.accumulo.core.security.VisibilityConstraint    
+    root@instance vistest> user username
+    Enter password for user username: ********
+    username@instance vistest> insert row f4 q4 v4 -l spinach                                                                
+        Constraint Failures:
+            ConstraintViolationSummary(constrainClass:org.apache.accumulo.core.security.VisibilityConstraint, violationCode:2, violationDescription:User does not have authorization on column visibility, numberOfViolatingMutations:1)
+    username@instance vistest> insert row f4 q4 v4 -l spinach|broccoli
+    username@instance vistest> scan
+    row f1:q1 [A]    v1
+    row f2:q2 [A&B]    v2
+    row f3:q3 [(apple&carrot)|broccoli|spinach]    v3
+    row f4:q4 [spinach|broccoli]    v4
+    username@instance vistest> 
+    

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/docs/index.html
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/docs/index.html b/server/monitor/src/main/resources/docs/index.html
new file mode 100644
index 0000000..fa399fb
--- /dev/null
+++ b/server/monitor/src/main/resources/docs/index.html
@@ -0,0 +1,41 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<html>
+<head>
+<title>Accumulo Documentation</title>
+<link rel='stylesheet' type='text/css' href='documentation.css' media='screen'/>
+</head>
+<body>
+
+<h1>Apache Accumulo Documentation</h1>
+<ul>
+<li><a href=accumulo_user_manual.pdf>User Manual</a></li>
+<li><a href=administration.html>Administration</a></li>
+<li><a href=combiners.html>Combiners</a></li>
+<li><a href=constraints.html>Constraints</a></li>
+<li><a href=bulkIngest.html>Bulk Ingest</a></li>
+<li><a href=config.html>Configuration</a></li>
+<li><a href=isolation.html>Isolation</a></li>
+<li><a href=apidocs/index.html>Java API</a></li>
+<li><a href=lgroups.html>Locality Groups</a></li>
+<li><a href=timestamps.html>Timestamps</a></li>
+<li><a href=metrics.html>Metrics</a></li>
+<li><a href=distributedTracing.html>Distributed Tracing</a></li>
+</ul>
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/docs/isolation.html
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/docs/isolation.html b/server/monitor/src/main/resources/docs/isolation.html
new file mode 100644
index 0000000..d0e77cc
--- /dev/null
+++ b/server/monitor/src/main/resources/docs/isolation.html
@@ -0,0 +1,39 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<html>
+<head>
+<title>Accumulo Isolation</title>
+<link rel='stylesheet' type='text/css' href='documentation.css' media='screen'/>
+</head>
+<body>
+
+<h1>Apache Accumulo Documentation : Isolation</h1>
+
+<h3>Scanning</h3>
+
+<p>Accumulo supports the ability to present an isolated view of rows when scanning.  There are three possible ways that a row could change in accumulo :  
+<ul>
+ <li>a mutation applied to a table
+ <li>iterators executed as part of a minor or major compaction 
+ <li>bulk import of new files
+</ul>
+Isolation guarantees that either all or none of the changes made by these operations on a row are seen.  Use the <a href='apidocs/org/apache/accumulo/core/client/IsolatedScanner.html'>IsolatedScanner</a> to obtain an isolated view of an accumulo table.  When using the regular scanner it is possible to see a non isolated view of a row.  For example if a mutation modifies three columns, it is possible that you will only see two of those modifications.  With the isolated scanner either all three of the changes are seen or none.  For an example of this try running the <a href='apidocs/org/apache/accumulo/examples/simple/isolation/InterferenceTest.html'>InterferenceTest</a> example.  
+
+<p>At this time there is no client side isolation support for the <a href='apidocs/org/apache/accumulo/core/client/BatchScanner.html'>BatchScanner</a>.  You may consider using the <a href='apidocs/org/apache/accumulo/core/iterators/WholeRowIterator.html'>WholeRowIterator</a> with the  <a href='apidocs/org/apache/accumulo/core/client/BatchScanner.html'>BatchScanner</a> to achieve isolation though. This drawback of doing this is that entire rows are read into memory on the server side.  If a row is too big, it may crash a tablet server.  The <a href='apidocs/org/apache/accumulo/core/client/IsolatedScanner.html'>IsolatedScanner</a> buffers rows on the client side so a large row will not crash a tablet server.
+
+<h3>Iterators</h3>
+<p>When writing server side iterators for accumulo isolation is something to be aware of.  A scan time iterator in accumulo reads from a set of data sources.  While an iterator is reading data it has an isolated view.  However, after it returns a key/value it is possible that accumulo may switch data sources and re-seek the iterator.  This is done so that resources may be reclaimed.  When the user does not request isolation this can occur after any key is returned.  When a user request isolation this will only occur after a new row is returned, in which case it will re-seek to the very beginning of the next possible row.

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/docs/lgroups.html
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/docs/lgroups.html b/server/monitor/src/main/resources/docs/lgroups.html
new file mode 100644
index 0000000..0012ffb
--- /dev/null
+++ b/server/monitor/src/main/resources/docs/lgroups.html
@@ -0,0 +1,42 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<html>
+<head>
+<title>Accumulo Locality Groups</title>
+<link rel='stylesheet' type='text/css' href='documentation.css' media='screen'/>
+</head>
+<body>
+
+<h1>Apache Accumulo Documentation : Locality Groups</h1>
+
+<p>Accumulo supports locality groups similar to those described in the Big Table paper.  Locality groups allow vertical partitioning of data by column family.  This allows user to configure their tables such that scans over a subset of column families are much faster.  The Accumulo locality group model has the following features.
+
+<UL>
+ <LI>There is a default locality group that holds all column families not in a declared locality group.
+ <LI>No requirement to declare locality groups or column families at table creation.
+ <LI>Can change locality group configuration on the fly.
+</UL>
+
+
+<P>When the locality group configuration for a table is changed it has no effect on existing data.  All minor and major compactions that occur after the change will organize data into the new locality group structure.  As data is written into a table, it will cause minor and major compactions to occur.  Over time this will result in all data being organized according to the new locality groups.   If all data must be reorganized into the new locality groups immediately, this can be accomplished by forcing a full major compaction of the table.  Use the compact command in the shell to accomplish this. 
+
+<P>There are two ways to manipulate locality groups, via the shell or through the Java API.  From the shell use the getgroups and setgroups commands.  Through the API, <a href='apidocs/org/apache/accumulo/core/client/admin/TableOperations.html'>TableOperations</a> has the methods setLocalityGroups() and getLocalityGroups().
+
+<P>To limit scans to a set of locality groups, use the fetchColumnFamily() function on  <a href='apidocs/org/apache/accumulo/core/client/Scanner.html'>Scanner</a> or <a href='apidocs/org/apache/accumulo/core/client/BatchScanner.html'>BatchScanner</a>.  From the shell use scan with the -c option.  
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/docs/metrics.html
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/docs/metrics.html b/server/monitor/src/main/resources/docs/metrics.html
new file mode 100644
index 0000000..00f0a5b
--- /dev/null
+++ b/server/monitor/src/main/resources/docs/metrics.html
@@ -0,0 +1,182 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<html>
+<head>
+<title>Accumulo Metrics</title>
+<link rel='stylesheet' type='text/css' href='documentation.css' media='screen'/>
+</head>
+<body>
+
+<h1>Apache Accumulo Documentation : Metrics</h1>
+
+As of version 1.2, metrics for the Master, Tablet Servers, and Loggers are available. A new configuration file, accumulo-metrics.xml, is located in the conf directory and can
+be modified to turn metrics collection on or off, and to enable file logging if desired. This file can be modified at runtime and the changes will be seen after a few seconds.
+Except where specified all time values are in milliseconds.
+<h1>Master Metrics</h1>
+<p>JMX Object Name: org.apache.accumulo.server.metrics:type=MasterMetricsMBean,name= &lt;current thread name&gt;</p>
+<table>
+	<thead>
+		<tr><td>Method Name</td><td>Description</td></tr>
+	</thead>
+	<tbody>
+		<tr class="highlight"><td>public long getPingCount();</td><td>Number of pings to tablet servers</td></tr>
+		<tr><td>public long getPingAvgTime();</td><td>Average time for each ping</td></tr>
+		<tr class="highlight"><td>public long getPingMinTime();</td><td>Minimum time for each ping</td></tr>
+		<tr><td>public long getPingMaxTime();</td><td>Maximum time for each ping</td></tr>
+		<tr class="highlight"><td>public String getTServerWithHighestPingTime();</td><td>tablet server with highest ping</td></tr>
+		<tr><td>public void reset();</td><td>Resets all counters to zero</td></tr>
+	</tbody>
+</table>
+<h1>Logging Server Metrics</h1>
+<p>JMX Object Name: org.apache.accumulo.server.metrics:type=LogWriterMBean,name= &lt;current thread name&gt;</p>
+<table>
+	<thead>
+		<tr><td>Method Name</td><td>Description</td></tr>
+	</thead>
+	<tbody>
+		<tr class="highlight"><td>public long getCloseCount();</td><td>Number of closed log files</td></tr>
+		<tr><td>public long getCloseAvgTime();</td><td>Average time to close a log file</td></tr>
+		<tr class="highlight"><td>public long getCloseMinTime();</td><td>Minimum time to close a log file</td></tr>
+		<tr><td>public long getCloseMaxTime();</td><td>Maximum time to close a log file</td></tr>
+		<tr class="highlight"><td>public long getCopyCount();</td><td>Number of log files copied</td></tr>
+		<tr><td>public long getCopyAvgTime();</td><td>Average time to copy a log file</td></tr>
+		<tr class="highlight"><td>public long getCopyMinTime();</td><td>Minimum time to copy a log file</td></tr>
+		<tr><td>public long getCopyMaxTime();</td><td>Maximum time to copy a log file</td></tr>
+		<tr class="highlight"><td>public long getCreateCount();</td><td>Number of log files created</td></tr>
+		<tr><td>public long getCreateMinTime();</td><td>Minimum time to create a log file</td></tr>
+		<tr class="highlight"><td>public long getCreateMaxTime();</td><td>Maximum time to create a log file</td></tr>
+		<tr><td>public long getCreateAvgTime();</td><td>Average time to create a log file</td></tr>
+		<tr class="highlight"><td>public long getLogAppendCount();</td><td>Number of times logs have been appended</td></tr>
+		<tr><td>public long getLogAppendMinTime();</td><td>Minimum time to append to a log file</td></tr>
+		<tr class="highlight"><td>public long getLogAppendMaxTime();</td><td>Maximum time to append to a log file</td></tr>
+		<tr><td>public long getLogAppendAvgTime();</td><td>Average time to append to a log file</td></tr>
+		<tr class="highlight"><td>public long getLogFlushCount();</td><td>Number of log file flushes</td></tr>
+		<tr><td>public long getLogFlushMinTime();</td><td>Minimum time to flush a log file</td></tr>
+		<tr class="highlight"><td>public long getLogFlushMaxTime();</td><td>Maximum time to flush a log file</td></tr>
+		<tr><td>public long getLogFlushAvgTime();</td><td>Average time to flush a log file</td></tr>
+		<tr class="highlight"><td>public long getLogExceptionCount();</td><td>Number of log exceptions</td></tr>
+		<tr><td>public void reset();</td><td>Resets all counters to zero</td></tr>
+	</tbody>
+</table>
+<h1>Tablet Server Metrics</h1>
+<p>JMX Object Name: org.apache.accumulo.server.metrics:type=TabletServerMBean,name= &lt;current thread name&gt;</p>
+<table>
+	<thead>
+		<tr><td>Method Name</td><td>Description</td></tr>
+	</thead>
+	<tbody>
+		<tr class="highlight"><td>public int getOnlineCount();</td><td>Number of tablets online</td></tr>
+		<tr><td>public int getOpeningCount();</td><td>Number of tablets that are being opened</td></tr>
+		<tr class="highlight"><td>public int getUnopenedCount();</td><td>Number or unopened tablets</td></tr>
+		<tr><td>public int getMajorCompactions();</td><td>Number of Major Compactions currently running</td></tr>
+		<tr class="highlight"><td>public int getMajorCompactionsQueued();</td><td>Number of Major Compactions yet to run</td></tr>
+		<tr><td>public int getMinorCompactions();</td><td>Number of Minor Compactions currently running</td></tr>
+		<tr class="highlight"><td>public int getMinorCompactionsQueued();</td><td>Number of Minor Compactions yet to run</td></tr>
+		<tr><td>public int getShutdownStage();</td><td>Current stage in the shutdown process</td></tr>
+		<tr class="highlight"><td>public long getEntries();</td><td>Number of entries in all the tablets</td></tr>
+		<tr><td>public long getEntriesInMemory();</td><td>Number of entries in memory on all tablet servers</td></tr>
+		<tr class="highlight"><td>public long getQueries();</td><td>Number of queries currently running on all the tablet servers</td></tr>
+		<tr><td>public long getIngest();</td><td>Number of entries currently being ingested on all the tablet servers</td></tr>
+		<tr class="highlight"><td>public long getTotalMinorCompactions();</td><td>Number of Minor Compactions completed</td></tr>
+		<tr><td>public double getHoldTime();</td><td>Number of seconds that ingest is waiting for memory to be freed on tablet servers</td></tr>
+		<tr class="highlight"><td>public String getName();</td><td>Address of the master</td></tr>
+	</tbody>
+</table>
+<h1>Tablet Server Minor Compaction Metrics</h1>
+<p>JMX Object Name: org.apache.accumulo.server.metrics:type=TabletServerMinCMetricsMBean,name= &lt;current thread name&gt;</p>
+<table>
+	<thead>
+		<tr><td>Method Name</td><td>Description</td></tr>
+	</thead>
+	<tbody>
+		<tr class="highlight"><td>public long getMinorCompactionCount();</td><td>Number of completed Minor Compactions on all tablet servers</td></tr>
+		<tr><td>public long getMinorCompactionAvgTime();</td><td>Average time to complete Minor Compaction</td></tr>
+		<tr class="highlight"><td>public long getMinorCompactionMinTime();</td><td>Minimum time to complete Minor Compaction</td></tr>
+		<tr><td>public long getMinorCompactionMaxTime();</td><td>Maximum time to complete Minor Compaction</td></tr>
+		<tr class="highlight"><td>public long getMinorCompactionQueueCount();</td><td>Number of Minor Compactions yet to be run</td></tr>
+		<tr><td>public long getMinorCompactionQueueAvgTime();</td><td>Average time Minor Compaction is in the queue</td></tr>
+		<tr class="highlight"><td>public long getMinorCompactionQueueMinTime();</td><td>Minimum time Minor Compaction is in the queue</td></tr>
+		<tr><td>public long getMinorCompactionQueueMaxTime();</td><td>Maximum time Minor Compaction is in the queue</td></tr>
+		<tr class="highlight"><td>public void reset();</td><td>Resets all counters to zero</td></tr>
+	</tbody>
+</table>
+<h1>Tablet Server Scan Metrics</h1>
+<p>JMX Object Name: org.apache.accumulo.server.metrics:type=TabletServerScanMetricsMBean,name= &lt;current thread name&gt;</p>
+<table>
+	<thead>
+		<tr><td>Method Name</td><td>Description</td></tr>
+	</thead>
+	<tbody>
+		<tr class="highlight"><td>public long getScanCount();</td><td>Number of scans completed</td></tr>
+		<tr><td>public long getScanAvgTime();</td><td>Average time for scan operation</td></tr>
+		<tr class="highlight"><td>public long getScanMinTime();</td><td>Minimum time for scan operation</td></tr>
+		<tr><td>public long getScanMaxTime();</td><td>Maximum time for scan operation</td></tr>
+		<tr class="highlight"><td>public long getResultCount();</td><td>Number of scans that returned a result</td></tr>
+		<tr><td>public long getResultAvgSize();</td><td>Average size of scan result</td></tr>
+		<tr class="highlight"><td>public long getResultMinSize();</td><td>Minimum size of scan result</td></tr>
+		<tr><td>public long getResultMaxSize();</td><td>Maximum size of scan result</td></tr>
+		<tr class="highlight"><td>public void reset();</td><td>Resets all counters to zero</td></tr>
+	</tbody>
+</table>
+<h1>Tablet Server Update Metrics</h1>
+<p>JMX Object Name: org.apache.accumulo.server.metrics:type=TabletServerUpdateMetricsMBean,name= &lt;current thread name&gt;</p>
+<table>
+	<thead>
+		<tr><td>Method Name</td><td>Description</td></tr>
+	</thead>
+	<tbody>
+		<tr class="highlight"><td>public long getPermissionErrorCount();</td><td>Number of permission errors</td></tr>
+		<tr><td>public long getUnknownTabletErrorCount();</td><td>Number of unknown tablet errors</td></tr>
+		<tr class="highlight"><td>public long getMutationArrayAvgSize();</td><td>Average size of mutation array</td></tr>
+		<tr><td>public long getMutationArrayMinSize();</td><td>Minimum size of mutation array</td></tr>
+		<tr class="highlight"><td>public long getMutationArrayMaxSize();</td><td>Maximum size of mutation array</td></tr>
+		<tr><td>public long getCommitPrepCount();</td><td>Number of commit preparations</td></tr>
+		<tr class="highlight"><td>public long getCommitPrepMinTime();</td><td>Minimum time for commit preparation</td></tr>
+		<tr><td>public long getCommitPrepMaxTime();</td><td>Maximum time for commit preparatation</td></tr>
+		<tr class="highlight"><td>public long getCommitPrepAvgTime();</td><td>Average time for commit preparation</td></tr>
+		<tr><td>public long getConstraintViolationCount();</td><td>Number of constraint violations</td></tr>
+		<tr class="highlight"><td>public long getWALogWriteCount();</td><td>Number of writes to the Write Ahead Log</td></tr>
+		<tr><td>public long getWALogWriteMinTime();</td><td>Minimum time of a write to the Write Ahead Log</td></tr>
+		<tr class="highlight"><td>public long getWALogWriteMaxTime();</td><td>Maximum time of a write to the Write Ahead Log</td></tr>
+		<tr><td>public long getWALogWriteAvgTime();</td><td>Average time of a write to the Write Ahead Log</td></tr>
+		<tr class="highlight"><td>public long getCommitCount();</td><td>Number of commits</td></tr>
+		<tr><td>public long getCommitMinTime();</td><td>Minimum time for a commit</td></tr>
+		<tr class="highlight"><td>public long getCommitMaxTime();</td><td>Maximum time for a commit</td></tr>
+		<tr><td>public long getCommitAvgTime();</td><td>Average time for a commit</td></tr>
+		<tr class="highlight"><td>public void reset();</td><td>Resets all counters to zero</td></tr>
+	</tbody>
+</table>
+<h1>Thrift Server Metrics</h1>
+<p>JMX Object Name: org.apache.accumulo.server.metrics:type=ThriftMetricsMBean,name= &lt;thread name&gt;</p>
+<table>
+	<thead>
+		<tr><td>Method Name</td><td>Description</td></tr>
+	</thead>
+	<tbody>
+		<tr class="highlight"><td>public long getIdleCount();</td><td>Number of times the Thrift server has been idle</td></tr>
+		<tr><td>public long getIdleMinTime();</td><td>Minimum amount of time the Thrift server has been idle</td></tr>
+		<tr class="highlight"><td>public long getIdleMaxTime();</td><td>Maximum amount of time the Thrift server has been idle</td></tr>
+		<tr><td>public long getIdleAvgTime();</td><td>Average time the Thrift server has been idle</td></tr>
+		<tr class="highlight"><td>public long getExecutionCount();</td><td>Number of calls processed by the Thrift server</td></tr>
+		<tr><td>public long getExecutionMinTime();</td><td>Minimum amount of time executing method</td></tr>
+		<tr class="highlight"><td>public long getExecutionMaxTime();</td><td>Maximum amount of time executing method</td></tr>
+		<tr><td>public long getExecutionAvgTime();</td><td>Average time executing methods</td></tr>
+		<tr class="highlight"><td>public void reset();</td><td>Resets all counters to zero</td></tr>
+	</tbody>
+</table>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/docs/timestamps.html
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/docs/timestamps.html b/server/monitor/src/main/resources/docs/timestamps.html
new file mode 100644
index 0000000..52290c7
--- /dev/null
+++ b/server/monitor/src/main/resources/docs/timestamps.html
@@ -0,0 +1,160 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<html>
+<head>
+<title>Accumulo Timestamps</title>
+<link rel='stylesheet' type='text/css' href='documentation.css' media='screen'/>
+</head>
+<body>
+
+<h1>Apache Accumulo Documentation : Timestamps</h1>
+
+<p>Everything inserted into accumulo has a timestamp.  If the user does not
+set it, then the system will set the timestamp.  The timestamp is the last
+thing accumulo sorts on.  So when two keys have the same row, column family,
+column qualifier, and column visibility then the timestamp of the two keys is
+compared. 
+
+<p>Timestamps are sorted in descending order, so the most recent data comes
+first.  When a table is created in accumulo, by default it has a versioning
+iterator that only shows the most recent.  In the example below two identical
+things are inserted.  The scan after that only shows the most recent version.
+However when the versioning iterator configuration is changed, then both are
+seen.  When data is inserted with a lower timestamp than existing data, it will
+fall behind the existing data and may not be seen depending on the versioning
+settings.  This is why the insert made with a timestamp of 500 is not seen in
+the scan below.
+
+<p><pre>
+root@ac12&gt; createtable foo
+root@ac12 foo&gt; 
+root@ac12 foo&gt; 
+root@ac12 foo&gt; insert r1 cf1 cq1 value1                                   
+root@ac12 foo&gt; insert r1 cf1 cq1 value2
+root@ac12 foo&gt; scan -st
+r1 cf1:cq1 [] 1279906856203    value2
+root@ac12 foo&gt; config -t foo -f iterator                                  
+---------+---------------------------------------------+-----------------------------------------------------------------------------------------------------
+SCOPE    | NAME                                        | VALUE
+---------+---------------------------------------------+-----------------------------------------------------------------------------------------------------
+table    | table.iterator.majc.vers .................. | 20,org.apache.accumulo.core.iterators.VersioningIterator
+table    | table.iterator.majc.vers.opt.maxVersions .. | 1
+table    | table.iterator.minc.vers .................. | 20,org.apache.accumulo.core.iterators.VersioningIterator
+table    | table.iterator.minc.vers.opt.maxVersions .. | 1
+table    | table.iterator.scan.vers .................. | 20,org.apache.accumulo.core.iterators.VersioningIterator
+table    | table.iterator.scan.vers.opt.maxVersions .. | 1
+---------+---------------------------------------------+-----------------------------------------------------------------------------------------------------
+root@ac12 foo&gt; config -t foo -s table.iterator.scan.vers.opt.maxVersions=3
+root@ac12 foo&gt; config -t foo -s table.iterator.minc.vers.opt.maxVersions=3
+root@ac12 foo&gt; config -t foo -s table.iterator.majc.vers.opt.maxVersions=3
+root@ac12 foo&gt; scan -st
+r1 cf1:cq1 [] 1279906856203    value2
+r1 cf1:cq1 [] 1279906853170    value1
+root@ac12 foo&gt; insert -t 600 r1 cf1 cq1 value3
+root@ac12 foo&gt; insert -t 500 r1 cf1 cq1 value4
+root@ac12 foo&gt; scan -st
+r1 cf1:cq1 [] 1279906856203    value2
+r1 cf1:cq1 [] 1279906853170    value1
+r1 cf1:cq1 [] 600    value3
+root@ac12 foo&gt;
+
+</pre>
+
+<p>Deletes are special keys in accumulo that get sorted along will all the other
+data.  When a delete key is inserted, accumulo will not show anything that has
+a timestamp less than or equal to the delete key.  In the example below an
+insert is made with timestamp 5 and then a delete is inserted with timestamp 3.
+The scan after that show that the delete marker does not hide the key.  However
+when a delete is inserted with timestamp 5, then nothing can be seen.  Once a
+delete marker is inserted, it is there until a full major compaction occurs.
+That is why the insert made after the delete can not be seen.  The insert after
+the flush and compact commands can be seen because the delete marker is gone.
+The flush forced a minor compaction and compact forced a full major compaction.
+
+<p><pre>
+root@ac12&gt; createtable bar
+root@ac12 bar&gt; insert -t 5 r1 cf1 cq1 val1
+root@ac12 bar&gt; scan -st
+r1 cf1:cq1 [] 5    val1
+root@ac12 bar&gt; delete -t 3 r1 cf1 cq1     
+root@ac12 bar&gt; scan
+r1 cf1:cq1 []    val1
+root@ac12 bar&gt; scan -st
+r1 cf1:cq1 [] 5    val1
+root@ac12 bar&gt; delete -t 5 r1 cf1 cq1
+root@ac12 bar&gt; scan -st              
+root@ac12 bar&gt; insert -t 5 r1 cf1 cq1 val2
+root@ac12 bar&gt; scan -st
+root@ac12 bar&gt; flush -t bar
+23 14:01:36,587 [shell.Shell] INFO : Flush of table bar initiated...
+root@ac12 bar&gt; compact -t bar
+23 14:02:00,042 [shell.Shell] INFO : Compaction of table bar scheduled for 20100723140200EDT
+root@ac12 bar&gt; insert -t 5 r1 cf1 cq1 val1
+root@ac12 bar&gt; scan
+r1 cf1:cq1 []    val1
+</pre>
+
+<p>If two inserts are made into accumulo with the same row, column, and
+timestamp, then the behavior is non-deterministic.
+
+<p>Accumulo 1.2 introduces the concept of logical time.  This ensures that
+timestamps set by accumulo always move forward.  There have been many problems
+caused by tablet servers with different system times.  In the case where a
+tablet servers time is in the future, tablets hosted on that tablet server and
+then migrated will have future timestamps in their data.  This can cause newer
+keys to fall behind existing keys, which can result in seeing older data or not
+seeing data if a new key falls behind on old delete.  Logical time prevents
+this by ensuring that accumulo set time stamps never go backwards, on a per
+tablet basis.  So if a tablet servers time is a year in the future, then any
+tablet hosted there will generate timestamps a year in the future even when
+later hosted on a server with correct time. Logical time can be configured on a
+per table basis to either set time in millis or to use a per tablet counter.
+The per tablet counter gives unique one up time stamps on a per mutation
+basis. When using time in millis, if two things arrive within the same
+millisecond then both receive the same timestamp. 
+
+<p>The example below shows a table created using a per tablet counter for
+timestamps.  Two inserts are made, the first gets timestamp 0 the second 1.
+After that the table is split into two tablets and two more inserts are made.
+These inserts get the same timestamp because they are made on different
+tablets.    When the original tablet is split into two, the two child tablets
+inherit the next timestamp of their parent and start from there. So do not
+expect this configuration to offer unique timestamps across a table.  Its only
+purpose is to uniquely order events within a tablet.
+
+<p><pre>
+root@ac12 foo&gt; createtable -tl logical
+root@ac12 logical&gt; insert 000892 person name "John Doe"
+root@ac12 logical&gt; insert 003042 person name "Jane Doe"
+root@ac12 logical&gt; scan -st
+000892 person:name [] 0    John Doe
+003042 person:name [] 1    Jane Doe
+root@ac12 logical&gt;
+root@ac12 logical&gt; addsplits -t logical 002000
+root@ac12 logical&gt; insert 003042 person address "123 Somewhere"
+root@ac12 logical&gt; insert 000892 person address "123 Nowhere"  
+root@ac12 logical&gt; scan -st
+000892 person:address [] 2    123 Nowhere
+000892 person:name [] 0    John Doe
+003042 person:address [] 2    123 Somewhere
+003042 person:name [] 1    Jane Doe
+root@ac12 logical&gt; 
+ 
+</pre>
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/web/down.gif
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/web/down.gif b/server/monitor/src/main/resources/web/down.gif
new file mode 100644
index 0000000..087da1d
Binary files /dev/null and b/server/monitor/src/main/resources/web/down.gif differ

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/web/favicon.png
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/web/favicon.png b/server/monitor/src/main/resources/web/favicon.png
new file mode 100644
index 0000000..a632dab
Binary files /dev/null and b/server/monitor/src/main/resources/web/favicon.png differ

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/web/flot/LICENSE.txt
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/web/flot/LICENSE.txt b/server/monitor/src/main/resources/web/flot/LICENSE.txt
new file mode 100644
index 0000000..07d5b20
--- /dev/null
+++ b/server/monitor/src/main/resources/web/flot/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2007-2009 IOLA and Ole Laursen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/resources/web/flot/excanvas.js
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/resources/web/flot/excanvas.js b/server/monitor/src/main/resources/web/flot/excanvas.js
new file mode 100644
index 0000000..c40d6f7
--- /dev/null
+++ b/server/monitor/src/main/resources/web/flot/excanvas.js
@@ -0,0 +1,1427 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns only support repeat.
+// * Radial gradient are not implemented. The VML version of these look very
+//   different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+//   width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+//   Quirks mode will draw the canvas using border-box. Either change your
+//   doctype to HTML5
+//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+//   or use Box Sizing Behavior from WebFX
+//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Filling very large shapes (above 5000 points) is buggy.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+  var abs = m.abs;
+  var sqrt = m.sqrt;
+
+  // this is used for sub pixel precision
+  var Z = 10;
+  var Z2 = Z / 2;
+
+  /**
+   * This funtion is assigned to the <canvas> elements as element.getContext().
+   * @this {HTMLElement}
+   * @return {CanvasRenderingContext2D_}
+   */
+  function getContext() {
+    return this.context_ ||
+        (this.context_ = new CanvasRenderingContext2D_(this));
+  }
+
+  var slice = Array.prototype.slice;
+
+  /**
+   * Binds a function to an object. The returned function will always use the
+   * passed in {@code obj} as {@code this}.
+   *
+   * Example:
+   *
+   *   g = bind(f, obj, a, b)
+   *   g(c, d) // will do f.call(obj, a, b, c, d)
+   *
+   * @param {Function} f The function to bind the object to
+   * @param {Object} obj The object that should act as this when the function
+   *     is called
+   * @param {*} var_args Rest arguments that will be used as the initial
+   *     arguments when the function is called
+   * @return {Function} A new function that has bound this
+   */
+  function bind(f, obj, var_args) {
+    var a = slice.call(arguments, 2);
+    return function() {
+      return f.apply(obj, a.concat(slice.call(arguments)));
+    };
+  }
+
+  function encodeHtmlAttribute(s) {
+    return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+  }
+
+  function addNamespacesAndStylesheet(doc) {
+    // create xmlns
+    if (!doc.namespaces['g_vml_']) {
+      doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
+                         '#default#VML');
+
+    }
+    if (!doc.namespaces['g_o_']) {
+      doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
+                         '#default#VML');
+    }
+
+    // Setup default CSS.  Only add one style sheet per document
+    if (!doc.styleSheets['ex_canvas_']) {
+      var ss = doc.createStyleSheet();
+      ss.owningElement.id = 'ex_canvas_';
+      ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+          // default size is 300x150 in Gecko and Opera
+          'text-align:left;width:300px;height:150px}';
+    }
+  }
+
+  // Add namespaces and stylesheet at startup.
+  addNamespacesAndStylesheet(document);
+
+  var G_vmlCanvasManager_ = {
+    init: function(opt_doc) {
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var doc = opt_doc || document;
+        // Create a dummy element so that IE will allow canvas elements to be
+        // recognized.
+        doc.createElement('canvas');
+        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+      }
+    },
+
+    init_: function(doc) {
+      // find all canvas elements
+      var els = doc.getElementsByTagName('canvas');
+      for (var i = 0; i < els.length; i++) {
+        this.initElement(els[i]);
+      }
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function(el) {
+      if (!el.getContext) {
+        el.getContext = getContext;
+
+        // Add namespaces and stylesheet to document of the element.
+        addNamespacesAndStylesheet(el.ownerDocument);
+
+        // Remove fallback content. There is no way to hide text nodes so we
+        // just remove all childNodes. We could hide all elements and remove
+        // text nodes but who really cares about the fallback content.
+        el.innerHTML = '';
+
+        // do not use inline function because that will leak memory
+        el.attachEvent('onpropertychange', onPropertyChange);
+        el.attachEvent('onresize', onResize);
+
+        var attrs = el.attributes;
+        if (attrs.width && attrs.width.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setWidth_(attrs.width.nodeValue);
+          el.style.width = attrs.width.nodeValue + 'px';
+        } else {
+          el.width = el.clientWidth;
+        }
+        if (attrs.height && attrs.height.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setHeight_(attrs.height.nodeValue);
+          el.style.height = attrs.height.nodeValue + 'px';
+        } else {
+          el.height = el.clientHeight;
+        }
+        //el.getContext().setCoordsize_()
+      }
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    var el = e.srcElement;
+
+    switch (e.propertyName) {
+      case 'width':
+        el.getContext().clearRect();
+        el.style.width = el.attributes.width.nodeValue + 'px';
+        // In IE8 this does not trigger onresize.
+        el.firstChild.style.width =  el.clientWidth + 'px';
+        break;
+      case 'height':
+        el.getContext().clearRect();
+        el.style.height = el.attributes.height.nodeValue + 'px';
+        el.firstChild.style.height = el.clientHeight + 'px';
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var decToHex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+    o2.globalAlpha   = o1.globalAlpha;
+    o2.font          = o1.font;
+    o2.textAlign     = o1.textAlign;
+    o2.textBaseline  = o1.textBaseline;
+    o2.arcScaleX_    = o1.arcScaleX_;
+    o2.arcScaleY_    = o1.arcScaleY_;
+    o2.lineScale_    = o1.lineScale_;
+  }
+
+  var colorData = {
+    aliceblue: '#F0F8FF',
+    antiquewhite: '#FAEBD7',
+    aquamarine: '#7FFFD4',
+    azure: '#F0FFFF',
+    beige: '#F5F5DC',
+    bisque: '#FFE4C4',
+    black: '#000000',
+    blanchedalmond: '#FFEBCD',
+    blueviolet: '#8A2BE2',
+    brown: '#A52A2A',
+    burlywood: '#DEB887',
+    cadetblue: '#5F9EA0',
+    chartreuse: '#7FFF00',
+    chocolate: '#D2691E',
+    coral: '#FF7F50',
+    cornflowerblue: '#6495ED',
+    cornsilk: '#FFF8DC',
+    crimson: '#DC143C',
+    cyan: '#00FFFF',
+    darkblue: '#00008B',
+    darkcyan: '#008B8B',
+    darkgoldenrod: '#B8860B',
+    darkgray: '#A9A9A9',
+    darkgreen: '#006400',
+    darkgrey: '#A9A9A9',
+    darkkhaki: '#BDB76B',
+    darkmagenta: '#8B008B',
+    darkolivegreen: '#556B2F',
+    darkorange: '#FF8C00',
+    darkorchid: '#9932CC',
+    darkred: '#8B0000',
+    darksalmon: '#E9967A',
+    darkseagreen: '#8FBC8F',
+    darkslateblue: '#483D8B',
+    darkslategray: '#2F4F4F',
+    darkslategrey: '#2F4F4F',
+    darkturquoise: '#00CED1',
+    darkviolet: '#9400D3',
+    deeppink: '#FF1493',
+    deepskyblue: '#00BFFF',
+    dimgray: '#696969',
+    dimgrey: '#696969',
+    dodgerblue: '#1E90FF',
+    firebrick: '#B22222',
+    floralwhite: '#FFFAF0',
+    forestgreen: '#228B22',
+    gainsboro: '#DCDCDC',
+    ghostwhite: '#F8F8FF',
+    gold: '#FFD700',
+    goldenrod: '#DAA520',
+    grey: '#808080',
+    greenyellow: '#ADFF2F',
+    honeydew: '#F0FFF0',
+    hotpink: '#FF69B4',
+    indianred: '#CD5C5C',
+    indigo: '#4B0082',
+    ivory: '#FFFFF0',
+    khaki: '#F0E68C',
+    lavender: '#E6E6FA',
+    lavenderblush: '#FFF0F5',
+    lawngreen: '#7CFC00',
+    lemonchiffon: '#FFFACD',
+    lightblue: '#ADD8E6',
+    lightcoral: '#F08080',
+    lightcyan: '#E0FFFF',
+    lightgoldenrodyellow: '#FAFAD2',
+    lightgreen: '#90EE90',
+    lightgrey: '#D3D3D3',
+    lightpink: '#FFB6C1',
+    lightsalmon: '#FFA07A',
+    lightseagreen: '#20B2AA',
+    lightskyblue: '#87CEFA',
+    lightslategray: '#778899',
+    lightslategrey: '#778899',
+    lightsteelblue: '#B0C4DE',
+    lightyellow: '#FFFFE0',
+    limegreen: '#32CD32',
+    linen: '#FAF0E6',
+    magenta: '#FF00FF',
+    mediumaquamarine: '#66CDAA',
+    mediumblue: '#0000CD',
+    mediumorchid: '#BA55D3',
+    mediumpurple: '#9370DB',
+    mediumseagreen: '#3CB371',
+    mediumslateblue: '#7B68EE',
+    mediumspringgreen: '#00FA9A',
+    mediumturquoise: '#48D1CC',
+    mediumvioletred: '#C71585',
+    midnightblue: '#191970',
+    mintcream: '#F5FFFA',
+    mistyrose: '#FFE4E1',
+    moccasin: '#FFE4B5',
+    navajowhite: '#FFDEAD',
+    oldlace: '#FDF5E6',
+    olivedrab: '#6B8E23',
+    orange: '#FFA500',
+    orangered: '#FF4500',
+    orchid: '#DA70D6',
+    palegoldenrod: '#EEE8AA',
+    palegreen: '#98FB98',
+    paleturquoise: '#AFEEEE',
+    palevioletred: '#DB7093',
+    papayawhip: '#FFEFD5',
+    peachpuff: '#FFDAB9',
+    peru: '#CD853F',
+    pink: '#FFC0CB',
+    plum: '#DDA0DD',
+    powderblue: '#B0E0E6',
+    rosybrown: '#BC8F8F',
+    royalblue: '#4169E1',
+    saddlebrown: '#8B4513',
+    salmon: '#FA8072',
+    sandybrown: '#F4A460',
+    seagreen: '#2E8B57',
+    seashell: '#FFF5EE',
+    sienna: '#A0522D',
+    skyblue: '#87CEEB',
+    slateblue: '#6A5ACD',
+    slategray: '#708090',
+    slategrey: '#708090',
+    snow: '#FFFAFA',
+    springgreen: '#00FF7F',
+    steelblue: '#4682B4',
+    tan: '#D2B48C',
+    thistle: '#D8BFD8',
+    tomato: '#FF6347',
+    turquoise: '#40E0D0',
+    violet: '#EE82EE',
+    wheat: '#F5DEB3',
+    whitesmoke: '#F5F5F5',
+    yellowgreen: '#9ACD32'
+  };
+
+
+  function getRgbHslContent(styleString) {
+    var start = styleString.indexOf('(', 3);
+    var end = styleString.indexOf(')', start + 1);
+    var parts = styleString.substring(start + 1, end).split(',');
+    // add alpha if needed
+    if (parts.length == 4 && styleString.substr(3, 1) == 'a') {
+      alpha = Number(parts[3]);
+    } else {
+      parts[3] = 1;
+    }
+    return parts;
+  }
+
+  function percent(s) {
+    return parseFloat(s) / 100;
+  }
+
+  function clamp(v, min, max) {
+    return Math.min(max, Math.max(min, v));
+  }
+
+  function hslToRgb(parts){
+    var r, g, b;
+    h = parseFloat(parts[0]) / 360 % 360;
+    if (h < 0)
+      h++;
+    s = clamp(percent(parts[1]), 0, 1);
+    l = clamp(percent(parts[2]), 0, 1);
+    if (s == 0) {
+      r = g = b = l; // achromatic
+    } else {
+      var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+      var p = 2 * l - q;
+      r = hueToRgb(p, q, h + 1 / 3);
+      g = hueToRgb(p, q, h);
+      b = hueToRgb(p, q, h - 1 / 3);
+    }
+
+    return '#' + decToHex[Math.floor(r * 255)] +
+        decToHex[Math.floor(g * 255)] +
+        decToHex[Math.floor(b * 255)];
+  }
+
+  function hueToRgb(m1, m2, h) {
+    if (h < 0)
+      h++;
+    if (h > 1)
+      h--;
+
+    if (6 * h < 1)
+      return m1 + (m2 - m1) * 6 * h;
+    else if (2 * h < 1)
+      return m2;
+    else if (3 * h < 2)
+      return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+    else
+      return m1;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.charAt(0) == '#') {
+      str = styleString;
+    } else if (/^rgb/.test(styleString)) {
+      var parts = getRgbHslContent(styleString);
+      var str = '#', n;
+      for (var i = 0; i < 3; i++) {
+        if (parts[i].indexOf('%') != -1) {
+          n = Math.floor(percent(parts[i]) * 255);
+        } else {
+          n = Number(parts[i]);
+        }
+        str += decToHex[clamp(n, 0, 255)];
+      }
+      alpha = parts[3];
+    } else if (/^hsl/.test(styleString)) {
+      var parts = getRgbHslContent(styleString);
+      str = hslToRgb(parts);
+      alpha = parts[3];
+    } else {
+      str = colorData[styleString] || styleString;
+    }
+    return {color: str, alpha: alpha};
+  }
+
+  var DEFAULT_STYLE = {
+    style: 'normal',
+    variant: 'normal',
+    weight: 'normal',
+    size: 10,
+    family: 'sans-serif'
+  };
+
+  // Internal text style cache
+  var fontStyleCache = {};
+
+  function processFontStyle(styleString) {
+    if (fontStyleCache[styleString]) {
+      return fontStyleCache[styleString];
+    }
+
+    var el = document.createElement('div');
+    var style = el.style;
+    try {
+      style.font = styleString;
+    } catch (ex) {
+      // Ignore failures to set to invalid font.
+    }
+
+    return fontStyleCache[styleString] = {
+      style: style.fontStyle || DEFAULT_STYLE.style,
+      variant: style.fontVariant || DEFAULT_STYLE.variant,
+      weight: style.fontWeight || DEFAULT_STYLE.weight,
+      size: style.fontSize || DEFAULT_STYLE.size,
+      family: style.fontFamily || DEFAULT_STYLE.family
+    };
+  }
+
+  function getComputedStyle(style, element) {
+    var computedStyle = {};
+
+    for (var p in style) {
+      computedStyle[p] = style[p];
+    }
+
+    // Compute the size
+    var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+        fontSize = parseFloat(style.size);
+
+    if (typeof style.size == 'number') {
+      computedStyle.size = style.size;
+    } else if (style.size.indexOf('px') != -1) {
+      computedStyle.size = fontSize;
+    } else if (style.size.indexOf('em') != -1) {
+      computedStyle.size = canvasFontSize * fontSize;
+    } else if(style.size.indexOf('%') != -1) {
+      computedStyle.size = (canvasFontSize / 100) * fontSize;
+    } else if (style.size.indexOf('pt') != -1) {
+      computedStyle.size = fontSize / .75;
+    } else {
+      computedStyle.size = canvasFontSize;
+    }
+
+    // Different scaling between normal text and VML text. This was found using
+    // trial and error to get the same size as non VML text.
+    computedStyle.size *= 0.981;
+
+    return computedStyle;
+  }
+
+  function buildStyle(style) {
+    return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
+        style.size + 'px ' + style.family;
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case 'butt':
+        return 'flat';
+      case 'round':
+        return 'round';
+      case 'square':
+      default:
+        return 'square';
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+  function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = '#000';
+    this.fillStyle = '#000';
+
+    this.lineWidth = 1;
+    this.lineJoin = 'miter';
+    this.lineCap = 'butt';
+    this.miterLimit = Z * 1;
+    this.globalAlpha = 1;
+    this.font = '10px sans-serif';
+    this.textAlign = 'left';
+    this.textBaseline = 'alphabetic';
+    this.canvas = surfaceElement;
+
+    var el = surfaceElement.ownerDocument.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+    this.lineScale_ = 1;
+  }
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    if (this.textMeasureEl_) {
+      this.textMeasureEl_.removeNode(true);
+      this.textMeasureEl_ = null;
+    }
+    this.element_.innerHTML = '';
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    var cp1 = this.getCoords_(aCP1x, aCP1y);
+    var cp2 = this.getCoords_(aCP2x, aCP2y);
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  // Helper function that takes the already fixed cordinates.
+  function bezierCurveTo(self, cp1, cp2, p) {
+    self.currentPath_.push({
+      type: 'bezierCurveTo',
+      cp1x: cp1.x,
+      cp1y: cp1.y,
+      cp2x: cp2.x,
+      cp2y: cp2.y,
+      x: p.x,
+      y: p.y
+    });
+    self.currentX_ = p.x;
+    self.currentY_ = p.y;
+  }
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // the following is lifted almost directly from
+    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+    var cp = this.getCoords_(aCPx, aCPy);
+    var p = this.getCoords_(aX, aY);
+
+    var cp1 = {
+      x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+      y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+    };
+    var cp2 = {
+      x: cp1.x + (p.x - this.currentX_) / 3.0,
+      y: cp1.y + (p.y - this.currentY_) / 3.0
+    };
+
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= Z;
+    var arcType = aClockwise ? 'at' : 'wa';
+
+    var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+    var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+    var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+    var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+    // IE won't render arches drawn counter clockwise if xStart == xEnd.
+    if (xStart == xEnd && !aClockwise) {
+      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+                       // that can be represented in binary
+    }
+
+    var p = this.getCoords_(aX, aY);
+    var pStart = this.getCoords_(xStart, yStart);
+    var pEnd = this.getCoords_(xEnd, yEnd);
+
+    this.currentPath_.push({type: arcType,
+                           x: p.x,
+                           y: p.y,
+                           radius: aRadius,
+                           xStart: pStart.x,
+                           yStart: pStart.y,
+                           xEnd: pEnd.x,
+                           yEnd: pEnd.y});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_('gradient');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+                                                   aX1, aY1, aR1) {
+    var gradient = new CanvasGradient_('gradientradial');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.r0_ = aR0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    gradient.r1_ = aR1;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function(image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+
+    // to find the original width we overide the width and height
+    var oldRuntimeWidth = image.runtimeStyle.width;
+    var oldRuntimeHeight = image.runtimeStyle.height;
+    image.runtimeStyle.width = 'auto';
+    image.runtimeStyle.height = 'auto';
+
+    // get the original size
+    var w = image.width;
+    var h = image.height;
+
+    // and remove overides
+    image.runtimeStyle.width = oldRuntimeWidth;
+    image.runtimeStyle.height = oldRuntimeHeight;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw Error('Invalid number of arguments');
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = sw / 2;
+    var h2 = sh / 2;
+
+    var vmlStr = [];
+
+    var W = 10;
+    var H = 10;
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="', Z * W, ',', Z * H, '"',
+                ' coordorigin="0,0"' ,
+                ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1] ||
+        this.m_[1][1] != 1 || this.m_[1][0]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push('M11=', this.m_[0][0], ',',
+                  'M12=', this.m_[1][0], ',',
+                  'M21=', this.m_[0][1], ',',
+                  'M22=', this.m_[1][1], ',',
+                  'Dx=', mr(d.x / Z), ',',
+                  'Dy=', mr(d.y / Z), '');
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx + dw, dy);
+      var c3 = this.getCoords_(dx, dy + dh);
+      var c4 = this.getCoords_(dx + dw, dy + dh);
+
+      max.x = m.max(max.x, c2.x, c3.x, c4.x);
+      max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+                  'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+                  filter.join(''), ", sizingmethod='clip');");
+
+    } else {
+      vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', Z * dw, 'px;',
+                ' height:', Z * dh, 'px"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var W = 10;
+    var H = 10;
+    // Divide the shape into chunks if it's too long because IE has a limit
+    // somewhere for how long a VML shape can be. This simple division does
+    // not work with fills, only strokes, unfortunately.
+    var chunkSize = 5000;
+
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var j = 0; j < this.currentPath_.length; j += chunkSize) {
+      var lineStr = [];
+      var lineOpen = false;
+
+      lineStr.push('<g_vml_:shape',
+                   ' filled="', !!aFill, '"',
+                   ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+                   ' coordorigin="0,0"',
+                   ' coordsize="', Z * W, ',', Z * H, '"',
+                   ' stroked="', !aFill, '"',
+                   ' path="');
+
+      var newSeq = false;
+
+      for (var i = j; i < Math.min(j + chunkSize, this.currentPath_.length); i++) {
+        if (i % chunkSize == 0 && i > 0) { // move into position for next chunk
+          lineStr.push(' m ', mr(this.currentPath_[i-1].x), ',', mr(this.currentPath_[i-1].y));
+        }
+
+        var p = this.currentPath_[i];
+        var c;
+
+        switch (p.type) {
+          case 'moveTo':
+            c = p;
+            lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+            break;
+          case 'lineTo':
+            lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+            break;
+          case 'close':
+            lineStr.push(' x ');
+            p = null;
+            break;
+          case 'bezierCurveTo':
+            lineStr.push(' c ',
+                         mr(p.cp1x), ',', mr(p.cp1y), ',',
+                         mr(p.cp2x), ',', mr(p.cp2y), ',',
+                         mr(p.x), ',', mr(p.y));
+            break;
+          case 'at':
+          case 'wa':
+            lineStr.push(' ', p.type, ' ',
+                         mr(p.x - this.arcScaleX_ * p.radius), ',',
+                         mr(p.y - this.arcScaleY_ * p.radius), ' ',
+                         mr(p.x + this.arcScaleX_ * p.radius), ',',
+                         mr(p.y + this.arcScaleY_ * p.radius), ' ',
+                         mr(p.xStart), ',', mr(p.yStart), ' ',
+                         mr(p.xEnd), ',', mr(p.yEnd));
+            break;
+        }
+  
+  
+        // TODO: Following is broken for curves due to
+        //       move to proper paths.
+  
+        // Figure out dimensions so we can do gradient fills
+        // properly
+        if (p) {
+          if (min.x == null || p.x < min.x) {
+            min.x = p.x;
+          }
+          if (max.x == null || p.x > max.x) {
+            max.x = p.x;
+          }
+          if (min.y == null || p.y < min.y) {
+            min.y = p.y;
+          }
+          if (max.y == null || p.y > max.y) {
+            max.y = p.y;
+          }
+        }
+      }
+      lineStr.push(' ">');
+  
+      if (!aFill) {
+        appendStroke(this, lineStr);
+      } else {
+        appendFill(this, lineStr, min, max);
+      }
+  
+      lineStr.push('</g_vml_:shape>');
+  
+      this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+    }
+  };
+
+  function appendStroke(ctx, lineStr) {
+    var a = processStyle(ctx.strokeStyle);
+    var color = a.color;
+    var opacity = a.alpha * ctx.globalAlpha;
+    var lineWidth = ctx.lineScale_ * ctx.lineWidth;
+
+    // VML cannot correctly render a line if the width is less than 1px.
+    // In that case, we dilute the color to make the line look thinner.
+    if (lineWidth < 1) {
+      opacity *= lineWidth;
+    }
+
+    lineStr.push(
+      '<g_vml_:stroke',
+      ' opacity="', opacity, '"',
+      ' joinstyle="', ctx.lineJoin, '"',
+      ' miterlimit="', ctx.miterLimit, '"',
+      ' endcap="', processLineCap(ctx.lineCap), '"',
+      ' weight="', lineWidth, 'px"',
+      ' color="', color, '" />'
+    );
+  }
+
+  function appendFill(ctx, lineStr, min, max) {
+    var fillStyle = ctx.fillStyle;
+    var arcScaleX = ctx.arcScaleX_;
+    var arcScaleY = ctx.arcScaleY_;
+    var width = max.x - min.x;
+    var height = max.y - min.y;
+    if (fillStyle instanceof CanvasGradient_) {
+      // TODO: Gradients transformed with the transformation matrix.
+      var angle = 0;
+      var focus = {x: 0, y: 0};
+
+      // additional offset
+      var shift = 0;
+      // scale factor for offset
+      var expansion = 1;
+
+      if (fillStyle.type_ == 'gradient') {
+        var x0 = fillStyle.x0_ / arcScaleX;
+        var y0 = fillStyle.y0_ / arcScaleY;
+        var x1 = fillStyle.x1_ / arcScaleX;
+        var y1 = fillStyle.y1_ / arcScaleY;
+        var p0 = ctx.getCoords_(x0, y0);
+        var p1 = ctx.getCoords_(x1, y1);
+        var dx = p1.x - p0.x;
+        var dy = p1.y - p0.y;
+        angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+        // The angle should be a non-negative number.
+        if (angle < 0) {
+          angle += 360;
+        }
+
+        // Very small angles produce an unexpected result because they are
+        // converted to a scientific notation string.
+        if (angle < 1e-6) {
+          angle = 0;
+        }
+      } else {
+        var p0 = ctx.getCoords_(fillStyle.x0_, fillStyle.y0_);
+        focus = {
+          x: (p0.x - min.x) / width,
+          y: (p0.y - min.y) / height
+        };
+
+        width  /= arcScaleX * Z;
+        height /= arcScaleY * Z;
+        var dimension = m.max(width, height);
+        shift = 2 * fillStyle.r0_ / dimension;
+        expansion = 2 * fillStyle.r1_ / dimension - shift;
+      }
+
+      // We need to sort the color stops in ascending order by offset,
+      // otherwise IE won't interpret it correctly.
+      var stops = fillStyle.colors_;
+      stops.sort(function(cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      var length = stops.length;
+      var color1 = stops[0].color;
+      var color2 = stops[length - 1].color;
+      var opacity1 = stops[0].alpha * ctx.globalAlpha;
+      var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
+
+      var colors = [];
+      for (var i = 0; i < length; i++) {
+        var stop = stops[i];
+        colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+      }
+
+      // When colors attribute is used, the meanings of opacity and o:opacity2
+      // are reversed.
+      lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+                   ' method="none" focus="100%"',
+                   ' color="', color1, '"',
+                   ' color2="', color2, '"',
+                   ' colors="', colors.join(','), '"',
+                   ' opacity="', opacity2, '"',
+                   ' g_o_:opacity2="', opacity1, '"',
+                   ' angle="', angle, '"',
+                   ' focusposition="', focus.x, ',', focus.y, '" />');
+    } else if (fillStyle instanceof CanvasPattern_) {
+      if (width && height) {
+        var deltaLeft = -min.x;
+        var deltaTop = -min.y;
+        lineStr.push('<g_vml_:fill',
+                     ' position="',
+                     deltaLeft / width * arcScaleX * arcScaleX, ',',
+                     deltaTop / height * arcScaleY * arcScaleY, '"',
+                     ' type="tile"',
+                     // TODO: Figure out the correct size to fit the scale.
+                     //' size="', w, 'px ', h, 'px"',
+                     ' src="', fillStyle.src_, '" />');
+       }
+    } else {
+      var a = processStyle(ctx.fillStyle);
+      var color = a.color;
+      var opacity = a.alpha * ctx.globalAlpha;
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+                   '" />');
+    }
+  }
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  };
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: 'close'});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    var m = this.m_;
+    return {
+      x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+      y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+    };
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    if (this.aStack_.length) {
+      copyState(this.aStack_.pop(), this);
+      this.m_ = this.mStack_.pop();
+    }
+  };
+
+  function matrixIsFinite(m) {
+    return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+        isFinite(m[1][0]) && isFinite(m[1][1]) &&
+        isFinite(m[2][0]) && isFinite(m[2][1]);
+  }
+
+  function setM(ctx, m, updateLineScale) {
+    if (!matrixIsFinite(m)) {
+      return;
+    }
+    ctx.m_ = m;
+
+    if (updateLineScale) {
+      // Get the line scale.
+      // Determinant of this.m_ means how much the area is enlarged by the
+      // transformation. So its square root can be used as a scale factor
+      // for width.
+      var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+      ctx.lineScale_ = sqrt(abs(det));
+    }
+  }
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), false);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), false);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), true);
+  };
+
+  contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+    var m1 = [
+      [m11, m12, 0],
+      [m21, m22, 0],
+      [dx,  dy,  1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), true);
+  };
+
+  contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+    var m = [
+      [m11, m12, 0],
+      [m21, m22, 0],
+      [dx,  dy,  1]
+    ];
+
+    setM(this, m, true);
+  };
+
+  /**
+   * The text drawing function.
+   * The maxWidth argument isn't taken in account, since no browser supports
+   * it yet.
+   */
+  contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+    var m = this.m_,
+        delta = 1000,
+        left = 0,
+        right = delta,
+        offset = {x: 0, y: 0},
+        lineStr = [];
+
+    var fontStyle = getComputedStyle(processFontStyle(this.font),
+                                     this.element_);
+
+    var fontStyleString = buildStyle(fontStyle);
+
+    var elementStyle = this.element_.currentStyle;
+    var textAlign = this.textAlign.toLowerCase();
+    switch (textAlign) {
+      case 'left':
+      case 'center':
+      case 'right':
+        break;
+      case 'end':
+        textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+        break;
+      case 'start':
+        textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+        break;
+      default:
+        textAlign = 'left';
+    }
+
+    // 1.75 is an arbitrary number, as there is no info about the text baseline
+    switch (this.textBaseline) {
+      case 'hanging':
+      case 'top':
+        offset.y = fontStyle.size / 1.75;
+        break;
+      case 'middle':
+        break;
+      default:
+      case null:
+      case 'alphabetic':
+      case 'ideographic':
+      case 'bottom':
+        offset.y = -fontStyle.size / 2.25;
+        break;
+    }
+
+    switch(textAlign) {
+      case 'right':
+        left = delta;
+        right = 0.05;
+        break;
+      case 'center':
+        left = right = delta / 2;
+        break;
+    }
+
+    var d = this.getCoords_(x + offset.x, y + offset.y);
+
+    lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
+                 ' coordsize="100 100" coordorigin="0 0"',
+                 ' filled="', !stroke, '" stroked="', !!stroke,
+                 '" style="position:absolute;width:1px;height:1px;">');
+
+    if (stroke) {
+      appendStroke(this, lineStr);
+    } else {
+      // TODO: Fix the min and max params.
+      appendFill(this, lineStr, {x: -left, y: 0},
+                 {x: right, y: fontStyle.size});
+    }
+
+    var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+                m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+    var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
+
+    lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
+                 ' offset="', skewOffset, '" origin="', left ,' 0" />',
+                 '<g_vml_:path textpathok="true" />',
+                 '<g_vml_:textpath on="true" string="',
+                 encodeHtmlAttribute(text),
+                 '" style="v-text-align:', textAlign,
+                 ';font:', encodeHtmlAttribute(fontStyleString),
+                 '" /></g_vml_:line>');
+
+    this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+  };
+
+  contextPrototype.fillText = function(text, x, y, maxWidth) {
+    this.drawText_(text, x, y, maxWidth, false);
+  };
+
+  contextPrototype.strokeText = function(text, x, y, maxWidth) {
+    this.drawText_(text, x, y, maxWidth, true);
+  };
+
+  contextPrototype.measureText = function(text) {
+    if (!this.textMeasureEl_) {
+      var s = '<span style="position:absolute;' +
+          'top:-20000px;left:0;padding:0;margin:0;border:none;' +
+          'white-space:pre;"></span>';
+      this.element_.insertAdjacentHTML('beforeEnd', s);
+      this.textMeasureEl_ = this.element_.lastChild;
+    }
+    var doc = this.element_.ownerDocument;
+    this.textMeasureEl_.innerHTML = '';
+    this.textMeasureEl_.style.font = this.font;
+    // Don't use innerHTML or innerText because they allow markup/whitespace.
+    this.textMeasureEl_.appendChild(doc.createTextNode(text));
+    return {width: this.textMeasureEl_.offsetWidth};
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function(image, repetition) {
+    return new CanvasPattern_(image, repetition);
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.x0_ = 0;
+    this.y0_ = 0;
+    this.r0_ = 0;
+    this.x1_ = 0;
+    this.y1_ = 0;
+    this.r1_ = 0;
+    this.colors_ = [];
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: aOffset,
+                       color: aColor.color,
+                       alpha: aColor.alpha});
+  };
+
+  function CanvasPattern_(image, repetition) {
+    assertImageIsValid(image);
+    switch (repetition) {
+      case 'repeat':
+      case null:
+      case '':
+        this.repetition_ = 'repeat';
+        break
+      case 'repeat-x':
+      case 'repeat-y':
+      case 'no-repeat':
+        this.repetition_ = repetition;
+        break;
+      default:
+        throwException('SYNTAX_ERR');
+    }
+
+    this.src_ = image.src;
+    this.width_ = image.width;
+    this.height_ = image.height;
+  }
+
+  function throwException(s) {
+    throw new DOMException_(s);
+  }
+
+  function assertImageIsValid(img) {
+    if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+      throwException('TYPE_MISMATCH_ERR');
+    }
+    if (img.readyState != 'complete') {
+      throwException('INVALID_STATE_ERR');
+    }
+  }
+
+  function DOMException_(s) {
+    this.code = this[s];
+    this.message = s +': DOM Exception ' + this.code;
+  }
+  var p = DOMException_.prototype = new Error;
+  p.INDEX_SIZE_ERR = 1;
+  p.DOMSTRING_SIZE_ERR = 2;
+  p.HIERARCHY_REQUEST_ERR = 3;
+  p.WRONG_DOCUMENT_ERR = 4;
+  p.INVALID_CHARACTER_ERR = 5;
+  p.NO_DATA_ALLOWED_ERR = 6;
+  p.NO_MODIFICATION_ALLOWED_ERR = 7;
+  p.NOT_FOUND_ERR = 8;
+  p.NOT_SUPPORTED_ERR = 9;
+  p.INUSE_ATTRIBUTE_ERR = 10;
+  p.INVALID_STATE_ERR = 11;
+  p.SYNTAX_ERR = 12;
+  p.INVALID_MODIFICATION_ERR = 13;
+  p.NAMESPACE_ERR = 14;
+  p.INVALID_ACCESS_ERR = 15;
+  p.VALIDATION_ERR = 16;
+  p.TYPE_MISMATCH_ERR = 17;
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+  DOMException = DOMException_;
+})();
+
+} // if