Skip to content
This repository was archived by the owner on Mar 11, 2019. It is now read-only.

Commit 930941a

Browse files
committed
Merge pull request #48 from Spirals-Team/feature/powerapi-cli
feat: Implement the CLI
2 parents ee3dc34 + 5672cd9 commit 930941a

File tree

9 files changed

+318
-1
lines changed

9 files changed

+318
-1
lines changed

powerapi-cli/build.sbt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name := "powerapi-cli"
2+
3+
lazy val downloadBluecoveApp = taskKey[File]("download-bluecove-app")
4+
lazy val downloadBluecoveGplApp = taskKey[File]("download-bluecove-gpl-app")
5+
6+
downloadBluecoveApp := {
7+
val locationBluecove = baseDirectory.value / "lib" / "bluecove-2.1.0.jar"
8+
if(!locationBluecove.getParentFile.exists()) locationBluecove.getParentFile.mkdirs()
9+
if(!locationBluecove.exists()) IO.download(url("https://bluecove.googlecode.com/files/bluecove-2.1.0.jar"), locationBluecove)
10+
locationBluecove
11+
}
12+
13+
downloadBluecoveGplApp := {
14+
val locationBluecoveGpl = baseDirectory.value / "lib" / "bluecove-gpl-2.1.0.jar"
15+
if(!locationBluecoveGpl.getParentFile.exists()) locationBluecoveGpl.getParentFile.mkdirs()
16+
if(!locationBluecoveGpl.exists()) IO.download(url("https://bluecove.googlecode.com/files/bluecove-gpl-2.1.0.jar"), locationBluecoveGpl)
17+
locationBluecoveGpl
18+
}
19+
20+
mappings in Universal += downloadBluecoveApp.value -> s"lib/${downloadBluecoveApp.value.name}"
21+
22+
mappings in Universal += downloadBluecoveGplApp.value -> s"lib/${downloadBluecoveGplApp.value.name}"
23+
24+
mappings in Universal ++= {
25+
((file("../") * "README*").get map {
26+
readmeFile: File =>
27+
readmeFile -> readmeFile.getName
28+
}) ++
29+
((file("../") * "LICENSE*").get map {
30+
licenseFile: File =>
31+
licenseFile -> licenseFile.getName
32+
})
33+
}
34+
35+
scriptClasspath ++= Seq("../conf", "../scripts")
36+
37+
NativePackagerKeys.executableScriptName := "powerapi"
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* This software is licensed under the GNU Affero General Public License, quoted below.
3+
*
4+
* This file is a part of PowerAPI.
5+
*
6+
* Copyright (C) 2011-2015 Inria, University of Lille 1.
7+
*
8+
* PowerAPI is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of
11+
* the License, or (at your option) any later version.
12+
*
13+
* PowerAPI is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with PowerAPI.
20+
*
21+
* If not, please consult http://www.gnu.org/licenses/agpl-3.0.html.
22+
*/
23+
package org.powerapi.app
24+
25+
import java.lang.management.ManagementFactory
26+
27+
import org.powerapi.core.target.{Application, All, Process, Target}
28+
import org.powerapi.reporter.{FileDisplay, JFreeChartDisplay, ConsoleDisplay}
29+
import org.powerapi.{PowerMonitoring, PowerMeter, PowerModule}
30+
import org.powerapi.core.power._
31+
import org.powerapi.module.cpu.dvfs.CpuDvfsModule
32+
import org.powerapi.module.cpu.simple.CpuSimpleModule
33+
import org.powerapi.module.libpfm.{LibpfmHelper, LibpfmCoreProcessModule, LibpfmCoreModule}
34+
import org.powerapi.module.powerspy.PowerSpyModule
35+
import scala.concurrent.duration.DurationInt
36+
import scala.sys
37+
import scala.sys.process.stringSeqToProcess
38+
39+
/**
40+
* PowerAPI CLI.
41+
*
42+
* @author <a href="mailto:maxime.colmant@gmail.com">Maxime Colmant</a>
43+
* @author <a href="mailto:l.huertas.pro@gmail.com">Loïc Huertas</a>
44+
*/
45+
object PowerAPI extends App {
46+
val modulesR = """(cpu-simple|cpu-dvfs|libpfm-core|libpfm-core-process|powerspy)(,(cpu-simple|cpu-dvfs|libpfm-core|libpfm-core-process|powerspy))*""".r
47+
val aggR = """max|min|geomean|logsum|mean|median|stdev|sum|variance""".r
48+
val durationR = """\d+""".r
49+
val pidR = """(\d+)""".r
50+
val appR = """(.+)""".r
51+
52+
@volatile var powerMeters = Seq[PowerMeter]()
53+
@volatile var monitors = Seq[PowerMonitoring]()
54+
55+
val shutdownHookThread = scala.sys.ShutdownHookThread {
56+
monitors.foreach(monitor => monitor.cancel())
57+
monitors = Seq()
58+
powerMeters.foreach(powerMeter => powerMeter.shutdown())
59+
powerMeters = Seq()
60+
}
61+
62+
def validateModules(str: String) = str match {
63+
case modulesR(_*) => true
64+
case _ => false
65+
}
66+
67+
implicit def modulesStrToPowerModules(str: String): Seq[PowerModule] = {
68+
(for(module <- str.split(",")) yield {
69+
module match {
70+
case "cpu-simple" => CpuSimpleModule()
71+
case "cpu-dvfs" => CpuDvfsModule()
72+
case "libpfm-core" => LibpfmCoreModule()
73+
case "libpfm-core-process" => LibpfmCoreProcessModule()
74+
case "powerspy" => PowerSpyModule()
75+
}
76+
}).toSeq
77+
}
78+
79+
def validateAgg(str: String): Boolean = str match {
80+
case aggR(_*) => true
81+
case _ => false
82+
}
83+
84+
implicit def aggStrToAggFunction(str: String): Seq[Power] => Power = {
85+
str match {
86+
case "max" => MAX
87+
case "min" => MIN
88+
case "geomean" => GEOMEAN
89+
case "logsum" => LOGSUM
90+
case "mean" => MEAN
91+
case "median" => MEDIAN
92+
case "stdev" => STDEV
93+
case "sum" => SUM
94+
case "variance" => VARIANCE
95+
}
96+
}
97+
98+
def validateDuration(str: String): Boolean = str match {
99+
case durationR(_*) => true
100+
case _ => false
101+
}
102+
103+
implicit def targetsStrToTargets(str: String): Seq[Target] = {
104+
val strTargets = if(str.split(",").contains("all")) {
105+
"all"
106+
}
107+
else str
108+
109+
(for(target <- strTargets.split(",")) yield {
110+
target match {
111+
case "" => Process(ManagementFactory.getRuntimeMXBean.getName.split("@")(0).toInt)
112+
case "all" => All
113+
case pidR(pid) => Process(pid.toInt)
114+
case appR(app) => Application(app)
115+
}
116+
}).toSeq
117+
}
118+
119+
def printHelp(): Unit = {
120+
val str =
121+
"""
122+
|PowerAPI, Spirals Team"
123+
|
124+
|Build a software-defined power meter. Do not forget to configure correctly the modules (see the documentation).
125+
|
126+
|usage: ./powerapi modules [cpu-simple|cpu-dvfs|libpfm-core|libpfm-core-proces|powerspy, ...] \
127+
| monitor --frequency [ms] --targets [pid, ..., app, ...)|all] --agg [max|min|geomean|logsum|mean|median|stdev|sum|variance] --[console,file [filepath],chart] \
128+
| duration [s]
129+
|
130+
|example: ./powerapi modules cpu-simple monitor --frequency 1000 --targets firefox --agg max --console monitor --targets chrome --agg max --console \
131+
| modules powerspy monitor --frequency 1000 --targets all --agg max --console \
132+
| duration 30
133+
""".stripMargin
134+
135+
println(str)
136+
}
137+
138+
def cli(options: List[Map[Symbol, Any]], duration: String, args: List[String]): (List[Map[Symbol, Any]], String) = args match {
139+
case Nil => (options, duration)
140+
case "modules" :: value :: "monitor" :: tail if validateModules(value) => {
141+
val (remainingArgs, monitors) = cliMonitorsSubcommand(List(), Map(), tail.map(_.toString))
142+
cli(options :+ Map('modules -> value, 'monitors -> monitors), duration, remainingArgs)
143+
}
144+
case "duration" :: value :: tail if validateDuration(value) => cli(options, value, tail)
145+
case option :: tail => println(s"unknown cli option $option"); sys.exit(1)
146+
}
147+
148+
def cliMonitorsSubcommand(options: List[Map[Symbol, Any]], currentMonitor: Map[Symbol, Any], args: List[String]): (List[String], List[Map[Symbol, Any]]) = args match {
149+
case Nil => (List(), options :+ currentMonitor)
150+
case "modules" :: value :: "monitor" :: tail if validateModules(value) => (List("modules", value, "monitor") ++ tail, options :+ currentMonitor)
151+
case "duration" :: value :: tail if validateDuration(value) => (List("duration", value) ++ tail, options :+ currentMonitor)
152+
case "monitor" :: tail => cliMonitorsSubcommand(options :+ currentMonitor, Map(), tail)
153+
case "--frequency" :: value :: tail if validateDuration(value) => cliMonitorsSubcommand(options, currentMonitor ++ Map('frequency -> value), tail)
154+
case "--targets" :: value :: tail => cliMonitorsSubcommand(options, currentMonitor ++ Map('targets -> value), tail)
155+
case "--agg" :: value :: tail if validateAgg(value) => cliMonitorsSubcommand(options, currentMonitor ++ Map('agg -> value), tail)
156+
case "--console" :: tail => cliMonitorsSubcommand(options, currentMonitor ++ Map('console -> "true"), tail)
157+
case "--file" :: value :: tail => cliMonitorsSubcommand(options, currentMonitor ++ Map('file -> value), tail)
158+
case "--chart" :: tail => cliMonitorsSubcommand(options, currentMonitor ++ Map('chart -> "true"), tail)
159+
case option :: tail => println(s"unknown monitor option $option"); sys.exit(1)
160+
}
161+
162+
if(args.size == 0) {
163+
printHelp()
164+
sys.exit(1)
165+
}
166+
167+
else {
168+
Seq("bash", "scripts/system.bash").!
169+
val (configuration, duration) = cli(List(), "3600", args.toList)
170+
171+
for(powerMeterConf <- configuration) {
172+
val modules = powerMeterConf('modules).toString
173+
if(modules.contains("libpfm-core") || modules.contains("libpfm-core-process")) LibpfmHelper.init()
174+
175+
val powerMeter = PowerMeter.loadModule(powerMeterConf('modules).toString: _*)
176+
powerMeters :+= powerMeter
177+
178+
for(monitorConf <- powerMeterConf('monitors).asInstanceOf[List[Map[Symbol, Any]]]) {
179+
val frequency = monitorConf.getOrElse('frequency, "1000").toString.toInt.milliseconds
180+
val targets: Seq[Target] = monitorConf.getOrElse('targets, "").toString.toLowerCase
181+
val agg: Seq[Power] => Power = aggStrToAggFunction(monitorConf.getOrElse('agg, "max").toString.toLowerCase)
182+
val console = monitorConf.getOrElse('console, "").toString
183+
val file = monitorConf.getOrElse('file, "").toString
184+
val chart = monitorConf.getOrElse('chart, "").toString
185+
186+
val monitor = powerMeter.monitor(frequency)(targets: _*)(agg)
187+
monitors :+= monitor
188+
189+
if(console != "") {
190+
val consoleDisplay = new ConsoleDisplay()
191+
monitor.to(consoleDisplay)
192+
}
193+
194+
if(file != "") {
195+
val fileDisplay = new FileDisplay(file)
196+
monitor.to(fileDisplay)
197+
}
198+
199+
if(chart != "") {
200+
val chartDisplay = new JFreeChartDisplay()
201+
monitor.to(chartDisplay)
202+
}
203+
}
204+
}
205+
206+
Thread.sleep(duration.toInt.seconds.toMillis)
207+
208+
val isLibpfmInit = configuration.count(powerMeterConf => powerMeterConf('modules).toString.contains("libpfm-core") || powerMeterConf('modules).toString.contains("libpfm-core-process")) != 0
209+
if(isLibpfmInit) LibpfmHelper.deinit()
210+
}
211+
212+
shutdownHookThread.start()
213+
shutdownHookThread.join()
214+
shutdownHookThread.remove()
215+
sys.exit(0)
216+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
akka {
2+
# Options: OFF, ERROR, WARNING, INFO, DEBUG
3+
loglevel = "error"
4+
5+
log-dead-letters-during-shutdown = off
6+
log-dead-letters = off
7+
8+
actor {
9+
guardian-supervisor-strategy = "org.powerapi.core.GuardianFailureStrategy"
10+
11+
debug {
12+
# Enable function of LoggingReceive, which is to log any received message at
13+
# DEBUG level
14+
receive = off
15+
16+
# Enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill and the like)
17+
autoreceive = off
18+
19+
# Enable DEBUG logging of actor lifecycle changes
20+
lifecycle = off
21+
22+
# Enable DEBUG logging of subscription changes on the eventStream
23+
event-stream = off
24+
}
25+
}
26+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include "akka"
2+
include "powerapi"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
3+
4+
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
5+
<appender name="console" class="org.apache.log4j.ConsoleAppender">
6+
<param name="Target" value="System.out"/>
7+
<layout class="org.apache.log4j.PatternLayout">
8+
<param name="ConversionPattern" value="[%level] [%d{MM/dd/yyyy HH:mm:ss.SSS}] [%t] [%logger{36}] - %msg%n"/>
9+
</layout>
10+
</appender>
11+
12+
<root>
13+
<priority value="error" />
14+
<appender-ref ref="console" />
15+
</root>
16+
</log4j:configuration>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Configuration status="INFO">
3+
<Appenders>
4+
<Console name="Console" target="SYSTEM_OUT">
5+
<PatternLayout pattern="[%level] [%d{MM/dd/yyyy HH:mm:ss.SSS}] [%t] [%logger{36}] - %msg%n" />
6+
</Console>
7+
</Appenders>
8+
<Loggers>
9+
<Root level="error">
10+
<AppenderRef ref="Console" />
11+
</Root>
12+
</Loggers>
13+
</Configuration>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Here is the required configuration. More details are available in the modules documentation.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
ulimit -n 4096
4+
5+
exit

project/PowerApiBuild.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ import com.typesafe.sbt.packager.archetypes.JavaAppPackaging
2424
import sbt._
2525

2626
object PowerApiBuild extends Build {
27-
lazy val powerapi = Project(id = "powerapi", base = file(".")).aggregate(powerapiCore, powerapiSampling)
27+
lazy val powerapi = Project(id = "powerapi", base = file(".")).aggregate(powerapiCore, powerapiCli, powerapiSampling)
2828

2929
lazy val powerapiCore = Project(id = "powerapi-core", base = file("powerapi-core"))
30+
lazy val powerapiCli = Project(id = "powerapi-cli", base = file("powerapi-cli")).dependsOn(powerapiCore % "compile -> compile; test -> test").enablePlugins(JavaAppPackaging)
3031
lazy val powerapiSampling = Project(id = "powerapi-sampling", base = file("powerapi-sampling")).dependsOn(powerapiCore % "compile -> compile; test -> test").enablePlugins(JavaAppPackaging)
3132
}

0 commit comments

Comments
 (0)