Friday, July 13, 2012

JAJMX (JAnalyseJMX) 0.4.0 released

JAJMX is high level scala JMX API. The goal is to simplify to the maximum JMX operations. ( ** project page **)

Latest changes :
  • ConnectException taken into account in RichMBean genericGetter in order to propagates connection failures and allow api users to restarts...
  • CompositeData & TabularData support enhancements
  • API CHANGES !! getOption disappear, get method now returns an Option. previous get method replaced by apply.
  • more tests cases
  • jmx mbeans queries was broken, now the query is build with an object name instead of a string
  • findMBeanServers made public and moved to JMX object
  • checkServiceURL added to JMXObject : checks if a jmx service url works.
  • getInt added to mbean
  • now using sbt 0.11.3
  • now using sbt-assembly 0.8.1
  • now using sbteclipse 2.1.0-RC1
  • now using scalatest 1.8
  • trap exceptions while building attributes map in RichMBean
  • various examples scripts fixes

jvm force gc script

#!/bin/sh
JAVA_OPTS=""
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.port=9999"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.authenticate=false"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.ssl=false"
exec java $JAVA_OPTS -jar jajmx.jar -nocompdaemon -usejavacp -savecompiled "$0" "$@"
!#

jajmx.JMX.once("127.0.0.1", 9999) { jmx =>
  jmx.memory map { _ set("Verbose", true)}  // Activate Verbose GC, to see on stdout next call effect 
  jmx.memory map {_ call "gc"}              // Force GC
}

jmx grep script

#!/bin/sh
JAVA_OPTS=""
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.port=9999"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.authenticate=false"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.ssl=false"
exec java $JAVA_OPTS -jar jajmx.jar -nocompdaemon -usejavacp -savecompiled "$0" "$@"
!#

import jajmx._

if (args.size == 0) {
  println("Usage   : jmxgrep host port searchMask1 ... searchMaskN")
  println("  no args given so now let's connecting to myself, and list my mbeans...") 
}
val host  = if (args.size>1) args(0) else "localhost"
val port  = if (args.size>2) args(1).toInt else 9999
val masks = if (args.size>3) args.toList.drop(2) map {s=>("(?i)"+s).r} else List.empty[util.matching.Regex]


def truncate(str:String, n:Int=60) = {
  val nonl=str.replaceAll("\n", " ").replaceAll("\r", "")
  if (nonl.size>n) nonl.take(n)+"..." else nonl
}

JMX.once(host, port) { jmx =>
  for(mbean <- jmx.mbeans ; attr <- mbean.attributes; value <- mbean.getString(attr)) {
    val found = List(mbean.name, attr.name, value) exists { item => masks exists {_.findFirstIn(item).isDefined } }
    if (masks.isEmpty || found) println("%s - %s = %s".format(mbean.name, attr.name, truncate(value)))
  }
}

Thursday, July 12, 2012

Parallel remote command ssh execution script

The following script executes the given commands on specified remote hosts in parallel. Notice that default actors corePoolSize parameter has been set to a higher value than the default one, as cpu usage for this script will remain low, actors will mostly wait for remote server results. (jassh.jar executable jar is available here - JASSH project page)
#!/bin/sh
exec java -jar jassh.jar -deprecation -savecompiled -usejavacp -nocompdaemon "$0" "$@"
!#

import fr.janalyse.ssh._
import util.Properties

if (args.size<2) {
  println("""usage : rexec.scala command [user[:password]@]host[:port] ...""")
  println("""  Of course prefer public key authentication, default behavior if no password is provided """)
  println("""  example : rexec.scala "hostname" 192.168.1.10 toto@192.168.1.11 toto@192.168.1.12:22""")
  System.exit(0)
}

// host | host:port | username@host |username:password@host | username@host:port | ... 
val serverRE="""(?:(\w+)(?:[:](.*))?@)?((?:(?:\d+[.]){3}\d+)|(?:\w+))(?:[:](\d+))?""".r

val cmd2exec=args.head
val servers = args.tail map {
  case serverRE(user, password, host, port) => 
    SSHOptions(
      host     = host,
      username = Option(user).getOrElse(Properties.userName),
      password = SSHPassword(Option(password)),
      port     = Option(port).map(_.toInt).getOrElse(22)
    )
  case notUnderstood => 
    throw new RuntimeException("Couln'd understand remote host description : "+notUnderstood)
}


def rexec(server:SSHOptions, cmd2exec:String):String = 
  SSH.once(server)(_.execute(cmd2exec))
     .split("\n")
     .map("%8s@%-16s: %s".format(server.username, server.host, _))
     .mkString("\n")
 
  
var sys=new scala.sys.SystemProperties()
sys+="actors.corePoolSize"->"25"


import actors.Actor._
val caller=self
for(server <- servers) actor { caller ! rexec(server, cmd2exec) }
for(_ <- servers) receive {case msg => println(msg) }

usage example :

$ ./rexec.scala "uname -r" test@127.0.0.1  test:testtest@192.168.1.200 
    test@127.0.0.1       : 3.2.1-gentoo-r2
    test@192.168.1.200   : 3.2.0-2-486

From what I've read, scala 2.10 will provide better solutions to tune threads pool, will update this post later.

Tuesday, July 10, 2012

Find the common basename (prefix) in a collection of String

Not tail recursive implementation

  def basename(names:Iterable[String]):Option[String] = {
    if (names.exists(_.size==0) || names.size<=1) None
    else {
      val firsts = names.map(_.head)
      if (firsts.toSet.size>1) None
      else {
        Some(firsts.head.toString+basename(names.map(_.tail)).getOrElse(""))
      }
    }
  }

Tail recursive implementation

  @annotation.tailrec
  def basename(names:Iterable[String], accu:Option[String]=None):Option[String] = {
    if (names.exists(_.size==0) || names.size<=1) accu
    else {
      val firsts = names.map(_.head)
      if (firsts.toSet.size>1) accu
      else basename(names.map(_.tail), Some(accu.getOrElse("")+firsts.head))
    }
  }

Usage example

$ scala
Welcome to Scala version 2.9.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_33).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :paste
// Entering paste mode (ctrl-D to finish)

  @annotation.tailrec
  def basename(names:Iterable[String], accu:Option[String]=None):Option[String] = {
    if (names.exists(_.size==0) || names.size<=1) accu
    else {
      val firsts = names.map(_.head)
      if (firsts.toSet.size>1) accu
      else basename(names.map(_.tail), Some(accu.getOrElse("")+firsts.head))
    }
  }

// Exiting paste mode, now interpreting.

basename: (names: Iterable[String], accu: Option[String])Option[String]

scala> basename(List("trucmuche", "trucmoche", "trucduq"))
res0: Option[String] = Some(truc)

scala> basename(List("was1", "was2"))
res1: Option[String] = Some(was)

scala> basename(List("was1"))
res2: Option[String] = None

Of course if we annotate with @tailrec the not tail recursive implementation we got the following error :
scala> :paste
// Entering paste mode (ctrl-D to finish)

@annotation.tailrec
def basename(names:Iterable[String]):Option[String] = {
    if (names.exists(_.size==0) || names.size<=1) None
    else {
      val firsts = names.map(_.head)
      if (firsts.toSet.size>1) None
      else {
        Some(firsts.head.toString+basename(names.map(_.tail)).getOrElse(""))
      }
    }
  }

// Exiting paste mode, now interpreting.

:15: error: could not optimize @tailrec annotated method basename: it contains a recursive call not in tail position
               Some(firsts.head.toString+basename(names.map(_.tail)).getOrElse(""))