Archive for July, 2010

Profiling a webapp

After a comment about the speed of the DAS registry responding to web service requests such as http://www.dasregistry.org/das/sources I decided to investigate. Looking at the code there is no obvious reason for slow responses as the data is read from a database seldomly and stored as objects in a cache- all responses are then pretty much filtered sources returned.

To investigate perfomance I used a shell script with 10 wget requests and the time command like first to my development machine from the same network and then from within the network to the production servers via the front ends:

/usr/bin/time sh testsources.sh   (note: /usr/bin/ was needed otherwise a different program called time was run which didn’t give the output expected)

4 results are below:

0.02user 0.10system 0:03.17elapsed 3%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6082minor)pagefaults 0swaps

0.02user 0.10system 0:03.29elapsed 3%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6085minor)pagefaults 0swaps

0.02user 0.09system 0:02.81elapsed 4%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6082minor)pagefaults 0swaps

0.02user 0.06system 0:03.36elapsed 2%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6087minor)pagefaults 0swaps

for the live servers:

/usr/bin/time sh testlivesources.sh

0.02user 0.07system 0:01.85elapsed 5%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6103minor)pagefaults 0swaps

0.02user 0.07system 0:01.58elapsed 6%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6097minor)pagefaults 0swaps

0.00user 0.06system 0:02.28elapsed 3%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6104minor)pagefaults 0swaps

0.02user 0.08system 0:03.15elapsed 3%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6094minor)pagefaults 0swaps

mac with 10 keywords queries:

0.01user 0.03system 0:01.10elapsed 4%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6113minor)pagefaults 0swaps

public registry with 10 keywords:

0.01user 0.04system 0:05.93elapsed 0%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6104minor)pagefaults 0swaps

0.01user 0.02system 0:01.13elapsed 3%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6107minor)pagefaults 0swaps

0.01user 0.05system 0:01.09elapsed 6%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6114minor)pagefaults 0swaps

then for some profiling using hprof that comes with Java and has documented in java 5.

use this before starting tomcat :

export CATALINA_OPTS=-agentlib:hprof=cpu=samples,depth=8,file=profile.txt

started tomcat on my dev machine and ran one of the times sh files in this case:

/usr/bin/time sh testsourceswithkeywords.sh

This produces the following output:

JAVA PROFILE 1.0.1, created Fri Jul 30 14:36:04 2010

Header for -agentlib:hprof (or -Xrunhprof) ASCII Output (J2SE 1.5 JVMTI based)

@(#)jvm.hprof.txt    1.3 04/02/09

Copyright (c) 2004 Sun Microsystems, Inc. All  Rights Reserved.

WARNING!  This file format is under development, and is subject to
change without notice.

This file contains the following types of records:

THREAD START
THREAD END      mark the lifetime of Java threads

TRACE           represents a Java stack trace.  Each trace consists
of a series of stack frames.  Other records refer to
TRACEs to identify (1) where object allocations have
taken place, (2) the frames in which GC roots were
found, and (3) frequently executed methods.

HEAP DUMP       is a complete snapshot of all live objects in the Java
heap.  Following distinctions are made:

ROOT    root set as determined by GC
CLS     classes
OBJ     instances
ARR     arrays

SITES           is a sorted list of allocation sites.  This identifies
the most heavily allocated object types, and the TRACE
at which those allocations occurred.

CPU SAMPLES     is a statistical profile of program execution.  The VM
periodically samples all running threads, and assigns
a quantum to active TRACEs in those threads.  Entries
in this record are TRACEs ranked by the percentage of
total quanta they consumed; top-ranked TRACEs are
typically hot spots in the program.

CPU TIME        is a profile of program execution obtained by measuring
the time spent in individual methods (excluding the time
spent in callees), as well as by counting the number of
times each method is called. Entries in this record are
TRACEs ranked by the percentage of total CPU time. The
“count” field indicates the number of times each TRACE
is invoked.

MONITOR TIME    is a profile of monitor contention obtained by measuring
the time spent by a thread waiting to enter a monitor.
Entries in this record are TRACEs ranked by the percentage
of total monitor contention time and a brief description
of the monitor.  The “count” field indicates the number of
times the monitor was contended at that TRACE.

MONITOR DUMP    is a complete snapshot of all the monitors and threads in
the System.

HEAP DUMP, SITES, CPU SAMPLES|TIME and MONITOR DUMP|TIME records are generated
at program exit.  They can also be obtained during program execution by typing
Ctrl-\ (on Solaris) or by typing Ctrl-Break (on Win32).

——–

THREAD START (obj=50000121, id = 200004, name=”Signal Dispatcher”, group=”system”)
THREAD START (obj=50000121, id = 200001, name=”main”, group=”main”)
THREAD START (obj=50000121, id = 200006, name=”ContainerBackgroundProcessor[StandardEngine[Catalina]]”, group=”main”)
THREAD START (obj=50000121, id = 200007, name=”http-8080-Acceptor-0″, group=”main”)
THREAD START (obj=50000a1f, id = 200008, name=”TP-Processor1″, group=”main”)
THREAD START (obj=50000a1f, id = 200009, name=”TP-Processor2″, group=”main”)
THREAD START (obj=50000a1f, id = 200010, name=”TP-Processor3″, group=”main”)
THREAD START (obj=50000a1f, id = 200011, name=”TP-Processor4″, group=”main”)
THREAD START (obj=50000121, id = 200012, name=”TP-Monitor”, group=”main”)
THREAD START (obj=50000121, id = 200013, name=”http-8080-1″, group=”main”)
THREAD START (obj=50000121, id = 200014, name=”http-8080-2″, group=”main”)
THREAD END (id = 200006)
THREAD END (id = 200001)
THREAD END (id = 200008)
THREAD START (obj=50000121, id = 200015, name=”DestroyJavaVM”, group=”main”)
THREAD END (id = 200012)
THREAD END (id = 200009)
THREAD START (obj=50000121, id = 200016, name=”Thread-0″, group=”main”)
THREAD END (id = 200010)
THREAD END (id = 200016)
THREAD END (id = 200015)
TRACE 300820:
java.net.PlainSocketImpl.socketAccept(PlainSocketImpl.java:Unknown line)
java.net.PlainSocketImpl.accept(PlainSocketImpl.java:384)
java.net.ServerSocket.implAccept(ServerSocket.java:450)
java.net.ServerSocket.accept(ServerSocket.java:421)
org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:61)
org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:310)
java.lang.Thread.run(Thread.java:613)
TRACE 300834:
java.net.PlainSocketImpl.socketAccept(PlainSocketImpl.java:Unknown line)
java.net.PlainSocketImpl.accept(PlainSocketImpl.java:384)
java.net.ServerSocket.implAccept(ServerSocket.java:450)
java.net.ServerSocket.accept(ServerSocket.java:421)
org.apache.catalina.core.StandardServer.await(StandardServer.java:389)
org.apache.catalina.startup.Catalina.await(Catalina.java:642)
org.apache.catalina.startup.Catalina.start(Catalina.java:602)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:Unknown line)
TRACE 300835:
java.net.PlainSocketImpl.socketAccept(PlainSocketImpl.java:Unknown line)
java.net.PlainSocketImpl.accept(PlainSocketImpl.java:384)
java.net.ServerSocket.implAccept(ServerSocket.java:450)
java.net.ServerSocket.accept(ServerSocket.java:421)
org.apache.jk.common.ChannelSocket.accept(ChannelSocket.java:306)
org.apache.jk.common.ChannelSocket.acceptConnections(ChannelSocket.java:660)
org.apache.jk.common.ChannelSocket$SocketAcceptor.runIt(ChannelSocket.java:870)
org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
TRACE 301068:
java.net.SocketInputStream.socketRead0(SocketInputStream.java:Unknown line)
java.net.SocketInputStream.read(SocketInputStream.java:129)
org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:730)
org.apache.coyote.http11.InternalInputBuffer.parseRequestLine(InternalInputBuffer.java:366)
org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:806)
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
java.lang.Thread.run(Thread.java:613)
TRACE 300883:
java.net.SocketInputStream.socketRead0(SocketInputStream.java:Unknown line)
java.net.SocketInputStream.read(SocketInputStream.java:129)
com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1392)
com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:1539)
com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:1930)
com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1168)
com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1279)
com.mysql.jdbc.Connection.execSQL(Connection.java:2281)
TRACE 300457:
java.lang.ClassLoader.defineClass1(ClassLoader.java:Unknown line)
java.lang.ClassLoader.defineClass(ClassLoader.java:676)
java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:1847)
org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:890)
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1354)
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1233)
java.lang.ClassLoader.loadClassInternal(ClassLoader.java:375)
TRACE 300136:
java.lang.ClassLoader.defineClass1(ClassLoader.java:Unknown line)
java.lang.ClassLoader.defineClass(ClassLoader.java:676)
java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
java.net.URLClassLoader.access$100(URLClassLoader.java:56)
java.net.URLClassLoader$1.run(URLClassLoader.java:195)
java.security.AccessController.doPrivileged(AccessController.java:Unknown line)
java.net.URLClassLoader.findClass(URLClassLoader.java:188)
TRACE 300480:
java.util.zip.ZipFile.getNextEntry(ZipFile.java:Unknown line)
java.util.zip.ZipFile.access$700(ZipFile.java:35)
java.util.zip.ZipFile$3.nextElement(ZipFile.java:421)
java.util.zip.ZipFile$3.nextElement(ZipFile.java:415)
java.util.jar.JarFile$1.nextElement(JarFile.java:223)
java.util.jar.JarFile$1.nextElement(JarFile.java:222)
org.apache.catalina.startup.TldConfig.tldScanJar(TldConfig.java:464)
org.apache.catalina.startup.TldConfig.execute(TldConfig.java:301)
TRACE 301051:
java.net.SocketInputStream.socketRead0(SocketInputStream.java:Unknown line)
java.net.SocketInputStream.read(SocketInputStream.java:129)
java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
java.io.BufferedInputStream.read(BufferedInputStream.java:235)
org.apache.axis.transport.http.HTTPSender.readHeadersFromSocket(HTTPSender.java:583)
org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:143)
org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)
org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)
TRACE 300907:
java.net.SocketInputStream.socketRead0(SocketInputStream.java:Unknown line)
java.net.SocketInputStream.read(SocketInputStream.java:129)
com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1392)
com.mysql.jdbc.MysqlIO.readPacket(MysqlIO.java:1414)
com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:284)
com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1326)
com.mysql.jdbc.Connection.execSQL(Connection.java:2281)
com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1634)
TRACE 300464:
java.util.zip.Inflater.inflateBytes(Inflater.java:Unknown line)
java.util.zip.Inflater.inflate(Inflater.java:230)
java.util.zip.InflaterInputStream.read(InflaterInputStream.java:128)
org.apache.catalina.loader.WebappClassLoader.findResourceInternal(WebappClassLoader.java:2093)
org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:1786)
org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:890)
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1354)
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1233)
TRACE 301131:
java.lang.Class.getDeclaredFields0(Class.java:Unknown line)
java.lang.Class.privateGetDeclaredFields(Class.java:2259)
java.lang.Class.getDeclaredFields(Class.java:1715)
org.apache.catalina.loader.WebappClassLoader.clearReferences(WebappClassLoader.java:1627)
org.apache.catalina.loader.WebappClassLoader.stop(WebappClassLoader.java:1524)
org.apache.catalina.loader.WebappLoader.stop(WebappLoader.java:707)
org.apache.catalina.core.StandardContext.stop(StandardContext.java:4557)
org.apache.catalina.core.ContainerBase.removeChild(ContainerBase.java:924)
TRACE 300403:
java.util.zip.ZipFile.getEntry(ZipFile.java:Unknown line)
java.util.zip.ZipFile.getEntry(ZipFile.java:252)
java.util.jar.JarFile.getEntry(JarFile.java:206)
java.util.jar.JarFile.getJarEntry(JarFile.java:189)
sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:674)
sun.misc.URLClassPath.getResource(URLClassPath.java:161)
java.net.URLClassLoader$1.run(URLClassLoader.java:192)
java.security.AccessController.doPrivileged(AccessController.java:Unknown line)
TRACE 300483:
java.util.zip.ZipEntry.initFields(ZipEntry.java:Unknown line)
java.util.zip.ZipEntry.<init>(ZipEntry.java:100)
java.util.zip.ZipFile$3.nextElement(ZipFile.java:437)
java.util.zip.ZipFile$3.nextElement(ZipFile.java:415)
java.util.jar.JarFile$1.nextElement(JarFile.java:223)
java.util.jar.JarFile$1.nextElement(JarFile.java:222)
org.apache.catalina.startup.TldConfig.tldScanJar(TldConfig.java:464)
org.apache.catalina.startup.TldConfig.execute(TldConfig.java:301)
TRACE 300920:
java.net.SocketOutputStream.socketWrite0(SocketOutputStream.java:Unknown line)
java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
java.net.SocketOutputStream.write(SocketOutputStream.java:136)
java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
com.mysql.jdbc.MysqlIO.send(MysqlIO.java:1765)
com.mysql.jdbc.MysqlIO.send(MysqlIO.java:1728)
com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1149)
TRACE 300458:
java.util.zip.ZipFile.getEntry(ZipFile.java:Unknown line)
java.util.zip.ZipFile.getEntry(ZipFile.java:252)
java.util.jar.JarFile.getEntry(JarFile.java:206)
java.util.jar.JarFile.getJarEntry(JarFile.java:189)
org.apache.catalina.loader.WebappClassLoader.findResourceInternal(WebappClassLoader.java:2001)
org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:1786)
org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:890)
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1354)
TRACE 300899:
java.net.SocketInputStream.socketRead0(SocketInputStream.java:Unknown line)
java.net.SocketInputStream.read(SocketInputStream.java:129)
com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1392)
com.mysql.jdbc.MysqlIO.readPacket(MysqlIO.java:1429)
com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:284)
com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1326)
com.mysql.jdbc.Connection.execSQL(Connection.java:2281)
com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1634)
TRACE 300450:
java.util.zip.ZipFile.open(ZipFile.java:Unknown line)
java.util.zip.ZipFile.<init>(ZipFile.java:203)
java.util.jar.JarFile.<init>(JarFile.java:134)
java.util.jar.JarFile.<init>(JarFile.java:99)
org.apache.catalina.loader.WebappLoader.setRepositories(WebappLoader.java:1009)
org.apache.catalina.loader.WebappLoader.start(WebappLoader.java:650)
org.apache.catalina.core.StandardContext.start(StandardContext.java:4212)
org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791)
TRACE 300456:
java.lang.ClassLoader.findBootstrapClass(ClassLoader.java:Unknown line)

…….

with some more like this:

CPU SAMPLES BEGIN (total = 8753) Fri Jul 30 14:36:32 2010
rank   self  accum   count trace method
1 24.33% 24.33%    2130 300820 java.net.PlainSocketImpl.socketAccept
2 24.23% 48.57%    2121 300834 java.net.PlainSocketImpl.socketAccept
3 24.23% 72.80%    2121 300835 java.net.PlainSocketImpl.socketAccept
4 15.99% 88.79%    1400 301068 java.net.SocketInputStream.socketRead0
5  2.40% 91.19%     210 300883 java.net.SocketInputStream.socketRead0
6  0.45% 91.64%      39 300457 java.lang.ClassLoader.defineClass1
7  0.22% 91.85%      19 300136 java.lang.ClassLoader.defineClass1
8  0.18% 92.04%      16 300480 java.util.zip.ZipFile.getNextEntry
9  0.16% 92.20%      14 301051 java.net.SocketInputStream.socketRead0
10  0.14% 92.33%      12 300907 java.net.SocketInputStream.socketRead0
11  0.13% 92.46%      11 300464 java.util.zip.Inflater.inflateBytes
12  0.13% 92.59%      11 301131 java.lang.Class.getDeclaredFields0
13  0.11% 92.70%      10 300403 java.util.zip.ZipFile.getEntry
14  0.11% 92.81%      10 300483 java.util.zip.ZipEntry.initFields
15  0.11% 92.93%      10 300920 java.net.SocketOutputStream.socketWrite0
16  0.10% 93.03%       9 300458 java.util.zip.ZipFile.getEntry
17  0.10% 93.13%       9 300899 java.net.SocketInputStream.socketRead0
18  0.07% 93.20%       6 300450 java.util.zip.ZipFile.open
19  0.07% 93.27%       6 300456 java.lang.ClassLoader.findBootstrapClass
20  0.06% 93.33%       5 300439 java.io.UnixFileSystem.getBooleanAttributes0
21  0.06% 93.39%       5 300892 java.net.SocketInputStream.socketRead0
22  0.06% 93.44%       5 300216 java.lang.ClassLoader.findBootstrapClass
23  0.06% 93.50%       5 300878 java.net.SocketInputStream.socketRead0
24  0.06% 93.56%       5 300481 java.util.zip.ZipFile.freeEntry
25  0.06% 93.61%       5 300766 java.lang.ref.Finalizer.invokeFinalizeMethod
26  0.05% 93.66%       4 300886 java.net.SocketInputStream.socketRead0
27  0.05% 93.71%       4 301010 sun.security.provider.SHA.implCompress
28  0.05% 93.75%       4 300887 java.net.PlainSocketImpl.socketAvailable
29  0.05% 93.80%       4 300747 java.lang.Throwable.fillInStackTrace
30  0.03% 93.83%       3 300494 java.lang.ClassLoader.defineClass1
31  0.03% 93.86%       3 300909 java.net.SocketInputStream.socketRead0
32  0.03% 93.90%       3 301081 java.io.PrintWriter.write
33  0.03% 93.93%       3 300215 java.util.zip.Inflater.inflateBytes
34  0.03% 93.97%       3 300664 java.io.UnixFileSystem.getBooleanAttributes0
35  0.03% 94.00%       3 300982 java.lang.String.intern
36  0.03% 94.04%       3 300940 java.net.SocketInputStream.socketRead0
37  0.03% 94.07%       3 300721 java.io.UnixFileSystem.getBooleanAttributes0
38  0.03% 94.10%       3 300943 java.lang.Throwable.fillInStackTrace
39  0.03% 94.14%       3 300427 java.lang.Throwable.fillInStackTrace
40  0.03% 94.17%       3 301062 sun.nio.cs.SingleByteEncoder.encodeArrayLoop
41  0.03% 94.21%       3 300698 java.lang.Throwable.fillInStackTrace
42  0.03% 94.24%       3 300953 java.lang.Throwable.fillInStackTrace
43  0.03% 94.28%       3 300335 java.lang.Object.clone
44  0.02% 94.30%       2 300328 sun.misc.Unsafe.defineClass
45  0.02% 94.32%       2 300445 java.lang.String.intern
46  0.02% 94.34%       2 300322 sun.misc.IOUtils.readFully
47  0.02% 94.37%       2 301102 java.util.ArrayList.indexOf
48  0.02% 94.39%       2 300906 java.net.SocketInputStream.read
49  0.02% 94.41%       2 300499 java.util.zip.Inflater.inflateBytes
50  0.02% 94.44%       2 300350 java.util.zip.ZipFile.getEntry
51  0.02% 94.46%       2 300500 java.util.zip.Inflater.end
52  0.02% 94.48%       2 301097 java.net.SocketOutputStream.socketWrite0
53  0.02% 94.50%       2 301075 sun.nio.cs.SingleByteEncoder.encodeArrayLoop
54  0.02% 94.53%       2 301095 java.lang.Object.clone
55  0.02% 94.55%       2 300843 java.util.zip.ZipFile.open
56  0.02% 94.57%       2 300425 java.lang.Throwable.fillInStackTrace
57  0.02% 94.60%       2 300762 java.lang.Object.clone
58  0.02% 94.62%       2 300960 java.net.SocketInputStream.socketRead0
59  0.02% 94.64%       2 300726 java.nio.Bits.copyToByteArray
60  0.02% 94.66%       2 300969 sun.reflect.Reflection.getClassAccessFlags
61  0.02% 94.69%       2 300791 java.io.UnixFileSystem.canonicalize0
62  0.02% 94.71%       2 301016 java.lang.ClassLoader.findLoadedClass0
63  0.02% 94.73%       2 300821 java.lang.Throwable.fillInStackTrace
64  0.02% 94.76%       2 300719 java.util.zip.Inflater.inflateBytes
65  0.02% 94.78%       2 300720 java.io.UnixFileSystem.canonicalize0
66  0.02% 94.80%       2 301116 org.biojava.services.das.servlets.RegistryServletResponseWriter.writeResponse
67  0.02% 94.82%       2 300933 java.lang.Character.toLowerCase
68  0.02% 94.85%       2 300939 java.lang.Character.isLetter
69  0.02% 94.87%       2 301107 java.io.PrintWriter.write
70  0.02% 94.89%       2 300928 java.net.SocketInputStream.socketRead0
71  0.01% 94.90%       1 300354 org.apache.catalina.core.ContainerBase.<init>
72  0.01% 94.92%       1 300418 java.net.URLClassLoader.findResources
73  0.01% 94.93%       1 300355 java.net.URLStreamHandler.parseURL
74  0.01% 94.94%       1 300356 java.lang.Object.hashCode
75  0.01% 94.95%       1 300248 com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.<init>
76  0.01% 94.96%       1 300205 com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.isTraceOn
77  0.01% 94.97%       1 300722 java.lang.Throwable.fillInStackTrace
78  0.01% 94.98%       1 300723 java.net.URI$Parser.parse
79  0.01% 95.00%       1 300724 org.apache.commons.discovery.tools.ClassUtils.findPublicStaticMethod
80  0.01% 95.01%       1 300725 org.apache.commons.discovery.tools.DiscoverClass.find
81  0.01% 95.02%       1 300157 java.util.Properties.loadConvert
82  0.01% 95.03%       1 300030 java.util.zip.Inflater.init
83  0.01% 95.04%       1 300729 java.net.URI.appendSchemeSpecificPart
84  0.01% 95.05%       1 300731 java.util.jar.JarFile.maybeInstantiateVerifier
85  0.01% 95.06%       1 300426 java.net.URLStreamHandler.parseURL
86  0.01% 95.08%       1 300733 java.lang.Class.getDeclaredFields0
87  0.01% 95.09%       1 300734 java.util.zip.ZipFile.close
88  0.01% 95.10%       1 300735 java.io.UnixFileSystem.getBooleanAttributes0
89  0.01% 95.11%       1 300736 java.io.UnixFileSystem.getLastModifiedTime
90  0.01% 95.12%       1 300737 org.apache.tomcat.util.digester.CallMethodRule.end
91  0.01% 95.13%       1 300738 com.sun.org.apache.xerces.internal.impl.dtd.DTDGrammar$QNameHashtable.put
92  0.01% 95.14%       1 300739 java.lang.Boolean.toBoolean
93  0.01% 95.16%       1 300428 sun.nio.cs.SingleByteEncoder.encodeArrayLoop
94  0.01% 95.17%       1 300742 java.lang.Class.getDeclaredConstructors0
95  0.01% 95.18%       1 300743 java.lang.ClassLoader.loadClass
96  0.01% 95.19%       1 300744 java.security.AccessController.doPrivileged
97  0.01% 95.20%       1 300429 java.lang.Class.forName0
98  0.01% 95.21%       1 300430 java.lang.Thread.currentThread
99  0.01% 95.22%       1 300748 org.apache.catalina.loader.WebappClassLoader.findClass
100  0.01% 95.24%       1 300749 org.apache.xerces.util.AugmentationsImpl.<init>
101  0.01% 95.25%       1 300750 org.apache.xerces.parsers.XML11Configuration.<init>
102  0.01% 95.26%       1 300751 java.util.zip.ZipFile.getInputStream
103  0.01% 95.27%       1 300752 org.apache.xerces.parsers.XML11Configuration.<init>
104  0.01% 95.28%       1 300431 java.lang.String.equals
105  0.01% 95.29%       1 300754 org.apache.xerces.parsers.XML11Configuration.<init>
106  0.01% 95.30%       1 300755 java.util.Arrays.fill
107  0.01% 95.32%       1 300756 org.apache.xerces.impl.XMLEntityScanner.scanLiteral
108  0.01% 95.33%       1 300757 org.apache.xerces.dom.DeferredDocumentImpl.getNodeObject
109  0.01% 95.34%       1 300758 java.util.Hashtable.<init>
110  0.01% 95.35%       1 300759 java.io.FileInputStream.open
111  0.01% 95.36%       1 300760 java.io.UnixFileSystem.getBooleanAttributes0
112  0.01% 95.37%       1 300761 com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.comment
113  0.01% 95.38%       1 300432 java.net.URLStreamHandler.parseURL
114  0.01% 95.40%       1 300764 java.util.zip.ZipFile.getEntry
115  0.01% 95.41%       1 300765 java.io.UnixFileSystem.getBooleanAttributes0
116  0.01% 95.42%       1 300767 java.net.URI$Parser.parse
117  0.01% 95.43%       1 300768 sun.reflect.NativeMethodAccessorImpl.invoke0
118  0.01% 95.44%       1 300769 com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.addDTDDefaultAttrsAndValidate
119  0.01% 95.45%       1 300770 javax.naming.NameImpl.extractComp
120  0.01% 95.46%       1 300771 java.lang.Class.getConstantPool
121  0.01% 95.48%       1 300772 sun.reflect.annotation.AnnotationParser.parseAnnotations
122  0.01% 95.49%       1 300433 com.sun.org.apache.xerces.internal.util.SymbolTable$Entry.<init>
123  0.01% 95.50%       1 300774 java.lang.Class.getDeclaredConstructors0
124  0.01% 95.51%       1 300775 org.biojava.servlets.dazzle.FeaturesHandler.<init>
125  0.01% 95.52%       1 300434 java.lang.ClassLoader.loadClass
126  0.01% 95.53%       1 300777 java.text.MessageFormat.makeFormat
127  0.01% 95.54%       1 300435 java.lang.String.toLowerCase
128  0.01% 95.56%       1 300779 org.apache.xerces.impl.XMLEntityManager.setScannerVersion
129  0.01% 95.57%       1 300780 org.apache.xerces.impl.XMLNSDocumentScannerImpl.createContentDispatcher
130  0.01% 95.58%       1 300781 java.net.URI$Parser.parse
131  0.01% 95.59%       1 300782 java.util.zip.ZipEntry.<init>
132  0.01% 95.60%       1 300436 java.lang.AbstractStringBuilder.append
133  0.01% 95.61%       1 300784 java.lang.ClassLoader.loadClass
134  0.01% 95.62%       1 300785 org.apache.xerces.parsers.AbstractDOMParser.startDocument
135  0.01% 95.64%       1 300786 java.lang.ClassLoader.findLoadedClass0
136  0.01% 95.65%       1 300787 org.apache.xerces.impl.XMLEntityScanner.skipSpaces
137  0.01% 95.66%       1 300788 org.apache.xerces.dom.DeferredDocumentImpl.setChunkIndex
138  0.01% 95.67%       1 300789 java.lang.ClassLoader.defineClass1
139  0.01% 95.68%       1 300790 sun.util.calendar.ZoneInfo.getTransitionIndex
140  0.01% 95.69%       1 300792 org.apache.catalina.loader.WebappClassLoader.findResourceInternal
141  0.01% 95.70%       1 300793 java.lang.AbstractStringBuilder.expandCapacity
142  0.01% 95.72%       1 300794 java.nio.HeapByteBuffer.<init>
143  0.01% 95.73%       1 300795 javax.naming.NameImpl.<init>
144  0.01% 95.74%       1 300796 java.text.MessageFormat.makeFormat
145  0.01% 95.75%       1 300797 org.apache.xerces.impl.XMLEntityManager.setupCurrentEntity
146  0.01% 95.76%       1 300798 org.apache.xerces.impl.XMLEntityScanner.scanLiteral
147  0.01% 95.77%       1 300799 org.apache.xerces.impl.dtd.DTDGrammar.element
148  0.01% 95.78%       1 300800 org.apache.xerces.impl.XMLScanner.scanPubidLiteral
149  0.01% 95.80%       1 300801 org.apache.xerces.impl.XMLVersionDetector.reset
150  0.01% 95.81%       1 300802 org.apache.xerces.impl.XMLDTDScannerImpl.scanElementDecl
151  0.01% 95.82%       1 300009 java.lang.Class.forName0
152  0.01% 95.83%       1 300567 sun.security.x509.CertificateExtensions.init
153  0.01% 95.84%       1 300805 java.security.CodeSource.matchCerts
154  0.01% 95.85%       1 300806 java.lang.AbstractStringBuilder.expandCapacity
155  0.01% 95.86%       1 300807 java.util.jar.JarFile$1.nextElement
156  0.01% 95.88%       1 300808 org.apache.catalina.loader.WebappClassLoader.loadClass
157  0.01% 95.89%       1 300809 javax.management.ObjectName.addProperty
158  0.01% 95.90%       1 300810 com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load
159  0.01% 95.91%       1 300811 org.apache.catalina.util.RequestUtil.URLDecode

Advertisements

Quartz for scheduling jobs with Java

The DAS Registry used to use a feature of the resin container which allowed servlets to be run at certain times of day/week/year or just run every few hours or days. This was achieved by setting run-at in the web.xml file of the webapp for the servlet.

As we are now moving to VMs and we are moving to tomcat for our production servers rather than resin this fascility does not exist. So after breifly considering rolling my own timer classes using the standard java classes I decided to check out the open source project Quartz (presumably named after the quartz watches timer mechanism).

I found the tutorials easy to get to grips with and within an hour or so had my first servlet converted to the new way of doing things.

We have 5 automated processes in the registry which are run at varying frequencies.

The things I needed to do were:

1) set up a quartz.properties file- and put in classpath

2) set up java class for Scheduling jobs in my case a servlet (so it could be initialized on tomcat startup using the servlet constructor).

3) create classes implementing the Job interface i.e. just having and execute method which will be called at the scheduled time.

4) there was already a database table to keep a track of when the last job of each type was run so there is persistence and I didnt’ need to use any of the quartz setup for that.

1) quartz.properties (put in resources dir which is on classpath set by eclipse build path):

org.quartz.scheduler.instanceName = RegistrySchedulerServlet
org.quartz.scheduler.instanceId = 1
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

2) scheduler

package org.biojava.services.das.servlets;

import javax.servlet.http.HttpServlet;

import org.apache.log4j.Logger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;

import uk.ac.sanger.dasregistry.timer.Archiver;
import uk.ac.sanger.dasregistry.timer.AutoValidation;
import uk.ac.sanger.dasregistry.timer.CleanLogs;
import uk.ac.sanger.dasregistry.timer.LuceneIndexer;
import uk.ac.sanger.dasregistry.timer.Mirroring;

/**
* Servlet implementation class RegistrySchedulerServlet
*/
public class RegistrySchedulerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
Scheduler scheduler;
public static Logger log=Logger.getLogger(” org.biojava.services.das.servlets”);

/**
* @see HttpServlet#HttpServlet()
*/
public RegistrySchedulerServlet() {
super();
try {
scheduler = StdSchedulerFactory.getDefaultScheduler();// default
// scheduler
// has been
// specified
// as this
// class in
// quartz.properties

// and start it off
scheduler.start();

// Define job instance
JobDetail indexerJob = new JobDetail(“indexing”, “group1”,
LuceneIndexer.class);
log.info(“indexing scheduled”);
JobDetail validationJob = new JobDetail(“autovalidation”, “group1”,
AutoValidation.class);
log.info(“autovalidation scheduled”);
JobDetail mirroringJob = new JobDetail(“mirroring”, “group1”,
Mirroring.class);
log.info(“mirrroring scheduled”);
JobDetail archiving = new JobDetail(“archiving”, “group1”,
Archiver.class);
log.info(“archiving scheduled”);
JobDetail cleanLogsJob = new JobDetail(“cleanLogs”, “group1”,
CleanLogs.class);
log.info(“clean logs scheduled”);
log.info(“scheduler setup”);
Trigger indexTrigger = TriggerUtils.makeDailyTrigger(“indexer”,
1, 30);//1.30am each day
// will go every 10 hours if set as only one machine in our cluster
// is allowed to run indexing
// comment out this servlet from develoment web.xml documents to
// prevent sending emails to some people
Trigger validationTrigger = TriggerUtils.makeHourlyTrigger(
“autovalidation”, 3, 1000000000);
// run every three hours or so – multiple machines can do this but
// only if last one longer than 3 hours ago (checked by DAO Admin
// checks that looks in database)
Trigger mirroringTrigger = TriggerUtils.makeWeeklyTrigger(
“mirroring”, 1, 1, 1);// run on Sunday at 1;01am
// jobs will be staggered as lastValidation etc times will be
// different.
Trigger archivingTrigger = TriggerUtils.makeDailyTrigger(
“archiving”, 4, 1); // 4:01am
// Trigger archivingTrigger =
// TriggerUtils.makeMinutelyTrigger(“archiving”, 4, 10); //4:01am
//Trigger cleanLogsTrigger=TriggerUtils.makeMinutelyTrigger(“cleanLogs”, 1,1000000);
Trigger cleanLogsTrigger=TriggerUtils.makeDailyTrigger(“cleanLogs”, 0,0);
// Schedule the job with the trigger
scheduler.scheduleJob(indexerJob, indexTrigger);
scheduler.scheduleJob(validationJob, validationTrigger);
scheduler.scheduleJob(mirroringJob, mirroringTrigger);
scheduler.scheduleJob(archiving, archivingTrigger);
scheduler.scheduleJob(cleanLogsJob, cleanLogsTrigger);

} catch (SchedulerException se) {
se.printStackTrace();
}

}

@Override
public void destroy() {
// TODO Auto-generated method stub
super.destroy();
try {
scheduler.shutdown();
System.out.println(“shutting down scheduler”);
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

3) example class for indexing:

package uk.ac.sanger.dasregistry.timer;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Timer;

import javax.servlet.ServletException;
import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.biojava.dasobert.dasregistry.DasSource;
import org.biojava.services.das.dao.DasSourceManager;
import org.biojava.services.das.registry.ConfigBean;
import org.biojava.services.das.registry.DasRegistrySql;
import org.biojava.services.das.servlets.ServletResponseIndexWriter;
import org.biojava.services.das2.Das1SourceDbProvider;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LuceneIndexer implements Job {
private static final long serialVersionUID = 1L;

public static Logger logger = Logger
.getLogger(“uk.ac.sanger.dasregistry.timer.LuceneIndexer”);

int contactParentFrequency;
Timer timer;
DasSourceManager dao;

boolean showValidationLog = true;
public static final int ONEHOUR = 1000 * 60 * 60;

// public static final int ONEHOUR = 1000 * 60 ;

/**
* @params validationFrequency in milli-seconds
*/

public void execute(JobExecutionContext arg0) throws JobExecutionException {
dao = new DasSourceManager();
// String computerToRunIndexOn =
// getInitParameter(“computerToRunIndexOn”);
// System.out.println(“computer to run index on=” +
// computerToRunIndexOn);
String computerName = “”;
try {
computerName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

ApplicationContext context = new ClassPathXmlApplicationContext(
“config_dasregistry.xml”);

DasRegistrySql registry = (DasRegistrySql) context
.getBean(“registryBean”);

ConfigBean config = (ConfigBean) context.getBean(“configBean”);
showValidationLog = config.isPrintValidationStatus();
System.out.println(“computer name=” + computerName);
if (computerName.equals(“mib21789i”)) {
System.out.println(“running indexing on computer=” + computerName);
// put some code to test if a specific computer name – then index if
// not don’t do any of the tests
// if the correct machine then the files can then be picked up by
// the getDasregistryIndexes.sh script that is run by a cron

Connection conn = null;
boolean doIndexing = false;
try {

DataSource dataSource = registry.getDataSource();
conn = dataSource.getConnection();

doIndexing = dao.doAdminChecks(conn, “lastIndex”,
“indexingStarted”, “indexing”);

} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

if (!doIndexing) {
logger
.info(” not running indexing due to dao admin checks… “);
return;
}

logger.info(“running auto indexing …”);
// String directory = getServletContext().getRealPath(“/”);

// System.out.println(“dir=” + directory);
try {
index(“/”);
} catch (ServletException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
releaseIndexingLock(registry);

registry = null;
}

}

private void releaseIndexingLock(DasRegistrySql registry) {
System.out.println(“releasing indexing lock”);
DataSource dataSource = registry.getDataSource();

Connection conn = null;
// boolean doValidate = false;
try {
conn = dataSource.getConnection();
dao.releaseAdminLock(conn, “indexing”, “lastIndex”);
// as we now update the valid_capabilities tables we want
// to tell the sources.xml returned by the registry it will need
// updating etc
// dao.pingLastModified(conn);

} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

private void index(String directory) throws ServletException, IOException {
System.out.println(“index called in Das2RegistryServlet”);
Das1SourceDbProvider das1provider = new Das1SourceDbProvider();

List<DasSource> sources = das1provider.getDasSources();
Map<String, List<Map<String, String>>> types = das1provider
.getAutoIdWithTypes(sources);

ServletResponseIndexWriter writer = new ServletResponseIndexWriter(
directory);

writer.writeIndexResponse(sources, types);

}

}

CORS

Today I added Cross Origin Resource Sharing headers (Access-Control-Allow-Origin: *) to the DAS Registry both for the responses to requests for example http://www.dasregistry.org/das/sources and to the testing of other DAS sources in the registry.  This means that JavaScript based DAS clients will not need to go via a proxy to send requests to DAS servers residing on a different server/domain to the one the client is running on. Currently JSDAS has to run through a proxy, a php one if in the standard download or the one the registry uses is a java based one with code like this:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String urlString=request.getParameter(“url”);
response.setHeader(“Content-Type”, “text/xml”);
//        response.setContentType(“text/xml”);
urlString=urlString.replace(” “,”+”);
//System.out.println(“urlString=”+urlString);
URLConnection connection = new URL(urlString).openConnection();

String contentEncoding = connection.getHeaderField(“Content-Encoding”);//should be”Content-Encoding”,”ISO-8859-1″)
//System.out.println(“content type=”+contentEncoding);
//if (contentType.startsWith(“text”)) {
//String charset = “ISO-8859-1”;//this encoding is what the registry uses so must be set here to override default;
//System.out.println(“charset=”+charset);
BufferedReader reader = null;
PrintWriter writer=response.getWriter();
try {
reader = new BufferedReader(new InputStreamReader( connection.getInputStream(), contentEncoding));
for (String line; (line = reader.readLine()) != null;) {
writer.println(line);
//System.out.println(line);
}
} finally {
if (reader != null) try { reader.close(); } catch (IOException logOrIgnore) {}
}

reader.close();
writer.flush();
writer.close();

//}

}  // end of main

http://dev.w3.org/2006/waf/access-control/  states:

“defines a mechanism to enable client-side cross-origin requests. Specifications that enable an API to make cross-origin requests to resources can use the algorithms defined by this specification. If such an API is used on http://example.org resources, a resource on http://hello-world.example can opt in using the mechanism described by this specification (e.g., specifying Access-Control-Allow-Origin: http://example.org as response header), which would allow that resource to be fetched cross-origin from http://example.org.”

I guess this is only supported in later versions of Firefox and safari….??

another useful link about this is here: http://saltybeagle.com/2009/09/cross-origin-resource-sharing-demo/

UCSC Data proxy

I’ve put up a UCSC sources.xml available from here : http://www.dasregistry.org/ucsc/das/sources generated from their dsn response and a table available from the UCSC that has information about their coordinate systems so I could guess what DAS coordinate system should be used with each source e.g. hg18 for the UCSC will equate to NCBI_36,Chromosome,Homo sapiens in DAS.

The DAS Registry uses this xml to generate entries in the registry for the 16 UCSC data sources that have been converted.

This should mean that ensembl and other data sources should be able to connect to the nearly 4000+ UCSC sources. There is one more thing that needs to be done however : that is allow users in ensembl to attach data sources based on type.

&type=bacEndPairs&type=ensGene needs to be added on the end of a normal features request otherwise too many features are returned and ensembl will time out.  Other DAS clients will find the same issue.

Today I spoke to Rachel who works at the UCSC and we hope to get some collaboration going so that the UCSC and ensembl “formatted” data sources are compatible. I’ve set up the registry so we can search types to enable people to find the UCSC source they want. The registry will filter the types returned that match the given keyword.

Sanger DAS Sources

Today the address http://das.sanger.ac.uk/das/sources was updated to respond with a valid sources document containing all the public Sanger DAS sources that have a valid coordinate system assigned (as opposed to older sources we serve that don’t have a coordinate system specified). This equates to currently 120 data sources.

This sources document will soon be mirrored by the registry as well so that sanger sources can be found there.

For some of the data sources the specific sources.xml response specified in the upcoming 1.6 spec for an individual source in response to example http://das.sanger.ac.uk/das/test16genes is supported – but currently most do not support the xml specified in 1.6. This will change in the next few months (is just complicated by the fact we serve this data from many servers and proserver and dazzle instances).

This should help when attaching das sources to clients like ensembl which currently use two different mechanisms – one for data sources that have a valid sources.xml like the das registry http://www.dasregistry.org/das/sources and the sanger sources which up until today not support the sources commmand with a proper response.

MyDas

yesterday I started to look at code for the DAS server MyDAS (http://code.google.com/p/mydas/) that was developed at the EBI. I wanted to use eclipse and the Web/tomcat/WTP setup to develop the mydas server.

Here are some very brief notes on setting up MyDas in eclipse which should at some point be translated into more technical and formal documentation on the biodas site:

mydas notes:

with eclipse use svn exploring then right-click on trunk/mydas/server_core and “check out as maven project”. Also do this again for trunk/mydas/MyDasTemplate

right-click on project and then select maven install from maven menu.
right-click on project again and select Run as server.
if you alter a java class in the MyDasTemplate project src dir then tomcat will auto restart and changes will take effect after 2 seconds. access using url http://localhost:8080/MyDasTemplate/das/sources – a xml document should be displayed, if you see text then use the browser to look at the source.
————–
How do we modify the mydas.jar which the MyDasTemplate  uses? mydas.jar is put in target dir under web-inf

I changed the mydas.jar that was set by  maven to the one from the mydas project (downloaded to eclipse as above project was) do this by right clicking on MyDasTemplate project and “build path”, “configure build path”,”java EE module dependencies”, uncheck the currently selected mydas.jar and “add jars”, select the mydas.jar from the mydas project dir.

To build the mydas project I right-clicked on the pom.xml then “run as” then “maven package” which did indeed build the mydas.jar within the mydas project under target dir. then run as “maven package” in the mydastemplate project pom.xml to make this change in the tomcat server.

system.outs also then appear in the eclipse console.

———————–

ensembl test – test region is wrong in tutorials should be chomosome name and range and coordinate system is wrong it’s not gene-id.

feature by id works http://localhost:8080/MyDasTemplate/das/ensemblTest/features?feature_id=ENSG00000160916

Is it a bird? Is it a plane?

Biodas man! A super hero of the Biodas community ??…. hmmmm