From 569f0ef47ab6a657dbf427dc8bc037492d72a258 Mon Sep 17 00:00:00 2001 From: mcolmant Date: Mon, 1 Dec 2014 13:18:42 +0100 Subject: [PATCH] feature: implement the PowerSPY module --- README.md | 9 +- build.sbt | 2 + .../module/powerspy/BluetoothDevice.java | 30 + .../module/powerspy/IEEE754Utils.java | 46 ++ .../powerapi/module/powerspy/PowerSpy.java | 30 + .../module/powerspy/PowerSpyEvent.java | 47 ++ .../module/powerspy/PowerSpyListenable.java | 28 + .../module/powerspy/PowerSpyListener.java | 27 + .../module/powerspy/PowerSpyVersion.java | 28 + .../module/powerspy/SimplePowerSpy.java | 520 ++++++++++++++++++ .../IdlePowerConfiguration.scala | 41 ++ src/main/scala/org/powerapi/core/Target.scala | 9 + .../org/powerapi/module/SensorChannel.scala | 5 +- .../module/powerspy/PSpyMetricsChannel.scala | 122 ++++ .../module/powerspy/PowerSpyChild.scala | 77 +++ .../module/powerspy/PowerSpyFormula.scala | 48 ++ .../module/powerspy/PowerSpySensor.scala | 108 ++++ .../module/procfs/ProcMetricsChannel.scala | 6 +- .../module/procfs/simple/CpuSensor.scala | 10 +- .../org/powerapi/module/FormulaSuite.scala | 18 +- .../powerspy/PowerSpyFormulaSuite.scala | 81 +++ .../module/powerspy/PowerSpySensorSuite.scala | 148 +++++ .../procfs/dvfs/DvfsCpuFormulaSuite.scala | 9 +- .../procfs/simple/SimpleCpuFormulaSuite.scala | 8 +- .../procfs/simple/SimpleCpuSensorSuite.scala | 4 +- 25 files changed, 1431 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/powerapi/module/powerspy/BluetoothDevice.java create mode 100644 src/main/java/org/powerapi/module/powerspy/IEEE754Utils.java create mode 100644 src/main/java/org/powerapi/module/powerspy/PowerSpy.java create mode 100644 src/main/java/org/powerapi/module/powerspy/PowerSpyEvent.java create mode 100644 src/main/java/org/powerapi/module/powerspy/PowerSpyListenable.java create mode 100644 src/main/java/org/powerapi/module/powerspy/PowerSpyListener.java create mode 100644 src/main/java/org/powerapi/module/powerspy/PowerSpyVersion.java create mode 100644 src/main/java/org/powerapi/module/powerspy/SimplePowerSpy.java create mode 100644 src/main/scala/org/powerapi/configuration/IdlePowerConfiguration.scala create mode 100644 src/main/scala/org/powerapi/module/powerspy/PSpyMetricsChannel.scala create mode 100644 src/main/scala/org/powerapi/module/powerspy/PowerSpyChild.scala create mode 100644 src/main/scala/org/powerapi/module/powerspy/PowerSpyFormula.scala create mode 100644 src/main/scala/org/powerapi/module/powerspy/PowerSpySensor.scala create mode 100644 src/test/scala/org/powerapi/module/powerspy/PowerSpyFormulaSuite.scala create mode 100644 src/test/scala/org/powerapi/module/powerspy/PowerSpySensorSuite.scala diff --git a/README.md b/README.md index 3cbc71a..744a968 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # PowerAPI -PowerAPI is a middleware toolkit for building software-defined power meters. Software-defined power meters are configurable software libraries that can estimate the power consumption of software in real-time. PowerAPI supports the acquisition of raw metrics from a wide diversity of sensors (*eg.*, physical meters, processor interfaces, hardware counters, OS counters) and the delivery of power consumptions via different channels (including file system, network, web, graphical). As a middleware toolkit, PowerAPI offers the capability of assembling power meters *«à la carte»* to accommodate user requirements. +PowerAPI is a middleware toolkit for building software-defined power meters. +Software-defined power meters are configurable software libraries that can estimate the power consumption of software in real-time. +PowerAPI supports the acquisition of raw metrics from a wide diversity of sensors (*eg.*, physical meters, processor interfaces, hardware counters, OS counters) and the delivery of power consumptions via different channels (including file system, network, web, graphical). +As a middleware toolkit, PowerAPI offers the capability of assembling power meters *«à la carte»* to accommodate user requirements. # About -PowerAPI is an open-source project developed by the [Spirals research group](https://team.inria.fr/spirals) (University of Lille 1 and Inria). +PowerAPI is an open-source project developed by the [Spirals research group](https://team.inria.fr/spirals) (University of Lille 1 and Inria) and fully managed with [sbt](http://www.scala-sbt.org/). ## Mailing list You can follow the latest news and asks questions by subscribing to our [mailing list](https://sympa.inria.fr/sympa/info/powerapi). @@ -17,6 +20,8 @@ We all stand on the shoulders of giants and get by with a little help from our f * [Akka](http://akka.io) (version 2.3.6 under [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)), for asynchronous processing * [Typesage Config](https://github.com/typesafehub/config) (version 1.2.1 under [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)), for reading configuration files. * [Apache log4j2](http://logging.apache.org/log4j/2.x/) (version 2.1 under [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)), for logging outside actors. +* [Bluecove](http://bluecove.org/) (version 2.1.0 under [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)), for interfacing the Bluetooth stack in Java (PowerSpy). +* [Bluecove-gpl](http://bluecove.org/bluecove-gpl/) (version 2.1.0 under [GPL licence](http://www.gnu.org/licenses/gpl.html)), for supporting Bluecove on Linux (PowerSpy). # Licence This software is licensed under the *GNU Affero General Public License*, quoted below. diff --git a/build.sbt b/build.sbt index 2278f37..1d9cc74 100644 --- a/build.sbt +++ b/build.sbt @@ -9,6 +9,8 @@ libraryDependencies ++= Seq( "com.typesafe" % "config" % "1.2.1", "org.apache.logging.log4j" % "log4j-api" % "2.1", "org.apache.logging.log4j" % "log4j-core" % "2.1", + "net.sf.bluecove" % "bluecove" % "2.1.0", + "net.sf.bluecove" % "bluecove-gpl" % "2.1.0", "com.typesafe.akka" %% "akka-testkit" % "2.3.6" % "test", "org.scalatest" %% "scalatest" % "2.2.2" % "test" ) diff --git a/src/main/java/org/powerapi/module/powerspy/BluetoothDevice.java b/src/main/java/org/powerapi/module/powerspy/BluetoothDevice.java new file mode 100644 index 0000000..03abf67 --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/BluetoothDevice.java @@ -0,0 +1,30 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +import java.io.Closeable; + +public interface BluetoothDevice extends Closeable { + public void send(String message); + public String recv(long timeout, boolean errorOnTimeout); +} diff --git a/src/main/java/org/powerapi/module/powerspy/IEEE754Utils.java b/src/main/java/org/powerapi/module/powerspy/IEEE754Utils.java new file mode 100644 index 0000000..5d95aa0 --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/IEEE754Utils.java @@ -0,0 +1,46 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +import java.nio.ByteOrder; + +public class IEEE754Utils { + public static Float fromString(String bits) throws NumberFormatException { + if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { + char[] bitsCharArray = bits.toCharArray(); + + for (int i = 0; i < bitsCharArray.length / 2; i += 2) { + char first = bitsCharArray[i]; + char second = bitsCharArray[i + 1]; + bitsCharArray[i] = bitsCharArray[bitsCharArray.length - i - 2]; + bitsCharArray[i + 1] = bitsCharArray[bitsCharArray.length - i - 1]; + bitsCharArray[bitsCharArray.length - i - 2] = first; + bitsCharArray[bitsCharArray.length - i - 1] = second; + } + + bits = String.valueOf(bitsCharArray); + } + + return Float.intBitsToFloat(Integer.valueOf(bits, 16)); + } +} diff --git a/src/main/java/org/powerapi/module/powerspy/PowerSpy.java b/src/main/java/org/powerapi/module/powerspy/PowerSpy.java new file mode 100644 index 0000000..9dacb34 --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/PowerSpy.java @@ -0,0 +1,30 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +public interface PowerSpy extends BluetoothDevice, PowerSpyListenable { + static final long DEFAULT_TIMEOUT = 3000; + + void startPowerMonitoring(); + void stopPowerMonitoring(); +} diff --git a/src/main/java/org/powerapi/module/powerspy/PowerSpyEvent.java b/src/main/java/org/powerapi/module/powerspy/PowerSpyEvent.java new file mode 100644 index 0000000..11ed622 --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/PowerSpyEvent.java @@ -0,0 +1,47 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +public class PowerSpyEvent { + private final Double currentRMS; + private final Float uScale; + private final Float iScale; + + public PowerSpyEvent(Double currentRMS, Float uScale, Float iScale) { + this.currentRMS = currentRMS; + this.uScale = uScale; + this.iScale = iScale; + } + + public Double getCurrentRMS() { + return currentRMS; + } + + public Float getUScale() { + return uScale; + } + + public Float getIScale() { + return iScale; + } +} diff --git a/src/main/java/org/powerapi/module/powerspy/PowerSpyListenable.java b/src/main/java/org/powerapi/module/powerspy/PowerSpyListenable.java new file mode 100644 index 0000000..df0951e --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/PowerSpyListenable.java @@ -0,0 +1,28 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +public interface PowerSpyListenable { + void addPowerSpyListener(PowerSpyListener listener); + void removePowerSpyListener(PowerSpyListener listener); +} diff --git a/src/main/java/org/powerapi/module/powerspy/PowerSpyListener.java b/src/main/java/org/powerapi/module/powerspy/PowerSpyListener.java new file mode 100644 index 0000000..4f05a22 --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/PowerSpyListener.java @@ -0,0 +1,27 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +public interface PowerSpyListener { + void dataUpdated(PowerSpyEvent event); +} diff --git a/src/main/java/org/powerapi/module/powerspy/PowerSpyVersion.java b/src/main/java/org/powerapi/module/powerspy/PowerSpyVersion.java new file mode 100644 index 0000000..2bf0c19 --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/PowerSpyVersion.java @@ -0,0 +1,28 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +public enum PowerSpyVersion { + POWERSPY_V1, + POWERSPY_V2 +} diff --git a/src/main/java/org/powerapi/module/powerspy/SimplePowerSpy.java b/src/main/java/org/powerapi/module/powerspy/SimplePowerSpy.java new file mode 100644 index 0000000..cd57016 --- /dev/null +++ b/src/main/java/org/powerapi/module/powerspy/SimplePowerSpy.java @@ -0,0 +1,520 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import javax.microedition.io.Connector; +import javax.microedition.io.StreamConnection; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class SimplePowerSpy implements PowerSpy { + private static final Logger LOG = LogManager.getLogger(); + + public static SimplePowerSpy connect(final String sppUrl, final PowerSpyVersion version) { + try { + StreamConnection connection = (StreamConnection) Connector.open(sppUrl); + BufferedReader input = new BufferedReader(new InputStreamReader(connection.openDataInputStream())); + PrintWriter output = new PrintWriter(connection.openOutputStream()); + + SimplePowerSpy powerSpy = new SimplePowerSpy(connection, version); + powerSpy.setInput(input); + powerSpy.setOutput(output); + return powerSpy; + } + catch (IOException|ClassCastException e) { + LOG.warn("{}", e.getMessage()); + } + + return null; + } + + protected StreamConnection connection; + protected PowerSpyVersion version = PowerSpyVersion.POWERSPY_V1; + protected Reader input; + protected Writer output; + protected ExecutorService ioExecutor = Executors.newSingleThreadExecutor(); + + protected SimplePowerSpy(final StreamConnection connection, final PowerSpyVersion version) { + this.connection = connection; + this.version = version; + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + close(); + } + catch (Exception e) { + LOG.warn("{}", e.getMessage()); + } + } + }); + } + + protected void setInput(final Reader input) { + this.input = input; + } + + protected void setOutput(final Writer output) { + this.output = output; + } + + @Override + public void send(final String message) { + try { + ioExecutor.execute(new Runnable() { + private void send() { + StringBuilder builder = new StringBuilder(); + builder.append("<"); + builder.append(message); + builder.append(">"); + try { + output.write(builder.toString()); + flushOutput(); + LOG.debug("{} sent", builder.toString()); + } + catch (IOException e) { + LOG.warn("{}", e.getMessage()); + } + } + + @SuppressWarnings("unused") + private void sleep(long timeout) { + try { + Thread.sleep(timeout); + } + catch (InterruptedException e) { + LOG.warn("{}", e.getMessage()); + } + } + + @Override + public void run() { + send(); + // See specification 3.2 "Error management" + // Edit: commented because it works fine without + // sleep(1000); + } + + @Override + public String toString() { + return "SEND (" + message + ")"; + } + }); + } + catch (RejectedExecutionException e) { + LOG.debug("{}", e.getMessage()); + } + } + + @Override + public String recv(long timeout, boolean errorOnTimeout) { + try { + Future in = ioExecutor.submit(new Callable() { + @Override + public String call() throws Exception { + StringBuilder buffer = new StringBuilder(); + boolean messageStarted = false; + char c = '>'; + do { + c = (char) input.read(); + if (c == '<') { + messageStarted = true; + } + else if (messageStarted && c != '>') { + buffer.append(c); + } + } while (!(messageStarted && c == '>')); + + return buffer.toString(); + } + + @Override + public String toString() { + return "RECV"; + } + }); + + String data = in.get(timeout, TimeUnit.MILLISECONDS); + LOG.debug("Received {}", data); + return data; + } + catch (InterruptedException e) { + if (errorOnTimeout) { + LOG.warn("{}", e.getMessage()); + } + LOG.debug("{}", "Received nothing"); + return null; + } + catch (ExecutionException e) { + if (errorOnTimeout) { + LOG.warn("{}", e.getMessage()); + } + LOG.debug("{}", "Received nothing"); + return null; + } + catch (TimeoutException e) { + if (errorOnTimeout) { + LOG.warn("{}", e.getMessage()); + } + LOG.debug("{}", "Received nothing"); + return null; + } + catch (RejectedExecutionException e) { + LOG.debug("{}", e.getMessage()); + return null; + } + } + + private boolean closed = false; + + @Override + public void close() { + if (!closed) { + // Stopping monitoring + stopPowerMonitoring(); + // reset(); + + // Stopping I/O tasks + List remainingTasks = ioExecutor.shutdownNow(); + if (!remainingTasks.isEmpty()) { + LOG.debug("{}", "Existing remaing tasks:"); + for (Runnable remainingTask : remainingTasks) { + LOG.debug("{}", remainingTask); + } + } + + LOG.debug("{}", "PowerSpy closed"); + + + // Closing output + flushOutput(); + try { + output.close(); + } + catch (IOException e) { + LOG.warn("{}", e.getMessage()); + } + + // Closing input + flushInput(); + try { + input.close(); + } + catch (IOException e) { + LOG.warn("{}", e.getMessage()); + } + + // Closing connection + try { + connection.close(); + } + catch (IOException e) { + LOG.warn("{}", e.getMessage()); + } + closed = true; + } + } + + private Float uScale = null; + + public Float uScale() { + if (uScale == null) { + StringBuilder uCalib = new StringBuilder(); + for (int i = 2; i <= 5; i++) { + send("V0" + i); + uCalib.append(recv(DEFAULT_TIMEOUT, true)); + } + try { + uScale = IEEE754Utils.fromString(uCalib.toString()); + } + catch (NumberFormatException e) { + LOG.warn("{}", e.getLocalizedMessage()); + uScale = null; + } + } + return uScale; + } + + private Float iScale = null; + + public Float iScale() { + if (iScale == null) { + StringBuilder iCalib = new StringBuilder(); + for (int i = 6; i <= 9; i++) { + send("V0" + i); + iCalib.append(recv(DEFAULT_TIMEOUT, true)); + } + try { + iScale = IEEE754Utils.fromString(iCalib.toString()); + } + catch (NumberFormatException e) { + LOG.warn("{}", e.getLocalizedMessage()); + iScale = null; + } + } + return iScale; + } + + private Float pScale = null; + + public Float pScale() { + if (pScale == null) { + if (uScale() != null && iScale() != null) { + pScale = uScale() * iScale(); + } + } + return pScale; + } + + private Collection listeners = new HashSet<>(); + + @Override + public void addPowerSpyListener(PowerSpyListener listener) { + listeners.add(listener); + } + + @Override + public void removePowerSpyListener(PowerSpyListener listener) { + listeners.remove(listener); + } + + private void flushOutput() { + try { + output.flush(); + } + catch (IOException e) { + LOG.warn("{}", e.getMessage()); + } + + } + + void flushInput() { + while(recv(DEFAULT_TIMEOUT, false) != null); + } + + /** + * Useful for the PowerSPY v1. + */ + void reset() { + flushOutput(); + send("R"); + try { + Thread.sleep(DEFAULT_TIMEOUT); + } + catch (InterruptedException e) { + LOG.warn("{}", e.getMessage()); + } + LOG.debug("{}", "PowerSpy: reset"); + } + + /** + * Useful for the PowerSPY v2. + */ + void start() { + flushOutput(); + send("S"); + try { + Thread.sleep(DEFAULT_TIMEOUT); + } + catch (InterruptedException e) { + LOG.warn("{}", e.getMessage()); + } + LOG.debug("{}", "PowerSpy: start"); + } + + void cancel() { + flushOutput(); + send("Q"); + send("C"); + try { + Thread.sleep(DEFAULT_TIMEOUT); + } + catch (InterruptedException e) { + LOG.warn("{}", e.getMessage()); + } + LOG.debug("{}", "PowerSpy: cancel"); + } + + public void fireDataUpdated(PowerSpyEvent event) { + for (PowerSpyListener listener : listeners) { + listener.dataUpdated(event); + } + } + + private SimplePowerSpyMonitoring powerMonitoring = null; + private ExecutorService monitoringExecutor = Executors.newSingleThreadExecutor(); + + @Override + public void startPowerMonitoring() { + if (powerMonitoring == null) { + powerMonitoring = new SimplePowerSpyMonitoring(); + monitoringExecutor.execute(powerMonitoring); + } + } + + @Override + public void stopPowerMonitoring() { + if (powerMonitoring != null) { + powerMonitoring.stopMonitoring(); + List remainingTasks = monitoringExecutor.shutdownNow(); + + LOG.debug("{}", "Existing remaining tasks:"); + for (Runnable remainingTask : remainingTasks) { + LOG.debug("{}", remainingTask); + } + } + } + + class SimplePowerSpyMonitoring implements Runnable { + + private boolean toContinue = true; + + private boolean initPScale() { + if (pScale() == null) { + LOG.warn("{}", "Unable to get uscale"); + return false; + } + return true; + } + + private boolean init() { + // PowerSpy initialization + if(version == PowerSpyVersion.POWERSPY_V1) reset(); + else if(version == PowerSpyVersion.POWERSPY_V2) start(); + else { + LOG.warn("{}", "Version unknown"); + return false; + } + + try { + Thread.sleep(SimplePowerSpy.DEFAULT_TIMEOUT); + } + catch (InterruptedException e) { + LOG.warn("{}", e.getMessage()); + } + + // Power scale initialization + if (!initPScale()) { + return false; + } + + LOG.debug("{}", "Monitoring initialized"); + return true; + } + + public void startMonitoring() { + // Power current: 50Hz (in most of the countries), thus we wanted to average 50 periods for 1 second (hexa: 32). + if(version == PowerSpyVersion.POWERSPY_V1) { + send("J32"); + } + else if(version == PowerSpyVersion.POWERSPY_V2) { + send("J0032"); + } + else { + LOG.warn("{}", "Version unknown"); + return; + } + + LOG.debug("{}", "Monitoring started"); + } + + public synchronized void stopMonitoring() { + setToContinue(false); + if(version == PowerSpyVersion.POWERSPY_V1) reset(); + else if(version == PowerSpyVersion.POWERSPY_V2) cancel(); + else { + LOG.warn("{}", "Version unknown"); + return; + } + flushInput(); + LOG.debug("{}", "Monitoring stopped"); + } + + private Double currentRMS() { + String recvData = null; + do { + recvData = recv(SimplePowerSpy.DEFAULT_TIMEOUT, true); + } while (recvData == null || recvData.split(" ").length != 5); + + try { + return Double.valueOf(Integer.valueOf(recvData.split(" ")[2], 16)); + } + catch (NumberFormatException e) { + LOG.warn("{}", e.getMessage()); + return -1d; + } + } + + public void monitor() { + fireDataUpdated(new PowerSpyEvent(currentRMS(), uScale(), iScale())); + } + + @Override + public void run() { + if (init()) { + startMonitoring(); + + try { + while (hasToContinue()) { + monitor(); + } + } + catch(NullPointerException e) { + LOG.warn("{}", "Monitoring stopped"); + } + } + else LOG.warn("{}", "Unable to initialize monitoring"); + } + + public synchronized boolean hasToContinue() { + return toContinue; + } + + public synchronized void setToContinue(boolean toContinue) { + this.toContinue = toContinue; + } + + @Override + public String toString() { + return "PowerSpyMonitoring"; + } + } +} diff --git a/src/main/scala/org/powerapi/configuration/IdlePowerConfiguration.scala b/src/main/scala/org/powerapi/configuration/IdlePowerConfiguration.scala new file mode 100644 index 0000000..53dc602 --- /dev/null +++ b/src/main/scala/org/powerapi/configuration/IdlePowerConfiguration.scala @@ -0,0 +1,41 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.configuration + +import org.powerapi.core.Configuration + +/** + * Ilde power / Configuration. + * + * @author Maxime Colmant + */ +trait IdlePowerConfiguration { + self: Configuration => + + import org.powerapi.core.ConfigValue + + lazy val idlePower = load { _.getDouble("powerapi.hardware.idle-power") } match { + case ConfigValue(idlePower) => idlePower + case _ => 0d + } +} diff --git a/src/main/scala/org/powerapi/core/Target.scala b/src/main/scala/org/powerapi/core/Target.scala index 714c297..10092d6 100644 --- a/src/main/scala/org/powerapi/core/Target.scala +++ b/src/main/scala/org/powerapi/core/Target.scala @@ -50,6 +50,15 @@ case class Process(pid: Long) extends Target */ case class Application(name: String) extends Target +/** + * Target usage ratio. + * + * @param ratio: usage ratio. + * + * @author Maxime Colmant + */ +case class TargetUsageRatio(ratio: Double) + /** * Monitoring target for the whole system. * diff --git a/src/main/scala/org/powerapi/module/SensorChannel.scala b/src/main/scala/org/powerapi/module/SensorChannel.scala index 5f817d0..a2232fc 100644 --- a/src/main/scala/org/powerapi/module/SensorChannel.scala +++ b/src/main/scala/org/powerapi/module/SensorChannel.scala @@ -24,7 +24,8 @@ package org.powerapi.module import java.util.UUID -import org.powerapi.core.{Channel, Message} +import org.powerapi.core.ClockChannel.ClockTick +import org.powerapi.core.{Target, Channel, Message} /** * Main sensor message. @@ -34,6 +35,8 @@ import org.powerapi.core.{Channel, Message} trait SensorReport extends Message { def topic: String def muid: UUID + def target: Target + def tick: ClockTick } /** diff --git a/src/main/scala/org/powerapi/module/powerspy/PSpyMetricsChannel.scala b/src/main/scala/org/powerapi/module/powerspy/PSpyMetricsChannel.scala new file mode 100644 index 0000000..df8a3ea --- /dev/null +++ b/src/main/scala/org/powerapi/module/powerspy/PSpyMetricsChannel.scala @@ -0,0 +1,122 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy + +import java.util.UUID + +import akka.actor.ActorRef +import org.powerapi.core.ClockChannel.ClockTick +import org.powerapi.core.{TargetUsageRatio, MessageBus, Target} +import org.powerapi.module.{SensorReport, SensorChannel} + +/** + * PSpyMetrics channel and messages. + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +object PSpyMetricsChannel extends SensorChannel { + /** + * Internal messages used between the Sensor and the Listener. + */ + object PSpyStart + case class PSpyChildMessage(rms: Double, uScale: Float, iScale: Float) { + def +(that: PSpyChildMessage): PSpyChildMessage = PSpyChildMessage(rms + that.rms, uScale + that.uScale, iScale + that.iScale) + def /(that: Int): Option[PSpyChildMessage] = if(that != 0) Some(PSpyChildMessage(rms / that, uScale / that, iScale / that)) else None + } + object PSpyChildMessage { + def avg(messages: List[PSpyChildMessage]): Option[PSpyChildMessage] = { + messages.foldLeft(PSpyChildMessage(0.0, 0f, 0f))((acc, msg) => acc + msg) / messages.size + } + } + + trait PSpyDataReport extends SensorReport + + /** + * PSpyAllDataReport is represented as a dedicated type of message. + * + * @param topic: subject used for routing the message. + * @param muid: monitor unique identifier (MUID), which is at the origin of the report flow. + * @param target: monitor target. + * @param rms: current root mean square. + * @param uScale: tension, voltage. + * @param iScale: intensity. + * @param tick: tick origin. + */ + case class PSpyAllDataReport(topic: String, + muid: UUID, + target: Target, + rms: Double, + uScale: Float, + iScale: Float, + tick: ClockTick) extends PSpyDataReport + + /** + * PSpyRatioDataReport is represented as a dedicated type of message. + * + * @param topic: subject used for routing the message. + * @param muid: monitor unique identifier (MUID), which is at the origin of the report flow. + * @param target: monitor target. + * @param targetRatio: target cpu percent usage. + * @param rms: current root mean square. + * @param uScale: tension, voltage. + * @param iScale: intensity. + * @param tick: tick origin. + */ + case class PSpyRatioDataReport(topic: String, + muid: UUID, + target: Target, + targetRatio: TargetUsageRatio, + rms: Double, + uScale: Float, + iScale: Float, + tick: ClockTick) extends PSpyDataReport + + /** + * Topic for communicating with the Formula actors. + */ + private val topicAll = "sensor:powerspy-all" + private val topicRatio = "sensor:powerspy-ratio" + + /** + * Publish a PSpyDataReport in the event bus. + */ + def publishPSpyDataReport(muid: UUID, target: Target, rms: Double, uScale: Float, iScale: Float, tick: ClockTick): MessageBus => Unit = { + publish(PSpyAllDataReport(topicAll, muid, target, rms, uScale, iScale, tick)) + } + + def publishPSpyDataReport(muid: UUID, target: Target, targetRatio: TargetUsageRatio, rms: Double, uScale: Float, iScale: Float, tick: ClockTick): MessageBus => Unit = { + publish(PSpyRatioDataReport(topicRatio, muid, target, targetRatio, rms, uScale, iScale, tick)) + } + + /** + * External method used by the Formula for interacting with the bus. + */ + def subscribePSpyAllDataReport: MessageBus => ActorRef => Unit = { + subscribe(topicAll) + } + + def subscribePSpyRatioDataReport: MessageBus => ActorRef => Unit = { + subscribe(topicRatio) + } +} diff --git a/src/main/scala/org/powerapi/module/powerspy/PowerSpyChild.scala b/src/main/scala/org/powerapi/module/powerspy/PowerSpyChild.scala new file mode 100644 index 0000000..2958415 --- /dev/null +++ b/src/main/scala/org/powerapi/module/powerspy/PowerSpyChild.scala @@ -0,0 +1,77 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy + +import java.io.{Reader, Writer} +import javax.microedition.io.StreamConnection +import akka.event.LoggingReceive +import org.powerapi.core.ActorComponent + +/** + * This child is responsible to react when the PowerSpy power meter produce messages. + */ +class PowerSpyChild(connection: StreamConnection, version: PowerSpyVersion, in: Reader, out: Writer) + extends SimplePowerSpy(connection, version) with ActorComponent { + + import org.powerapi.module.powerspy.PSpyMetricsChannel.{PSpyChildMessage, PSpyStart} + + setInput(in) + setOutput(out) + + override def fireDataUpdated(data: PowerSpyEvent): Unit = { + if(data != null) { + context.parent ! PSpyChildMessage(data.getCurrentRMS, data.getUScale, data.getIScale) + } + } + + def receive: PartialFunction[Any, Unit] = LoggingReceive { + case PSpyStart => startPowerMonitoring() + } orElse default + + override def postStop(): Unit = { + stopPowerMonitoring() + close() + super.postStop() + } +} + +/** + * Companion object for creating the underlying actor. + */ +object PowerSpyChild { + import akka.actor.Props + import java.io.{BufferedReader,InputStreamReader,PrintWriter} + import javax.microedition.io.Connector + + def props(sppUrl: String, version: PowerSpyVersion): Option[Props] = { + try { + val connection = Connector.open(sppUrl).asInstanceOf[StreamConnection] + val in = new BufferedReader(new InputStreamReader(connection.openInputStream())) + val out = new PrintWriter(connection.openOutputStream()) + Some(Props(new PowerSpyChild(connection, version, in, out))) + } + catch { + case _: Throwable => None + } + } +} diff --git a/src/main/scala/org/powerapi/module/powerspy/PowerSpyFormula.scala b/src/main/scala/org/powerapi/module/powerspy/PowerSpyFormula.scala new file mode 100644 index 0000000..d188673 --- /dev/null +++ b/src/main/scala/org/powerapi/module/powerspy/PowerSpyFormula.scala @@ -0,0 +1,48 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy + +import org.powerapi.configuration.IdlePowerConfiguration +import org.powerapi.core.MessageBus +import org.powerapi.module.FormulaComponent +import org.powerapi.module.powerspy.PSpyMetricsChannel.PSpyDataReport + +class PowerSpyFormula(eventBus: MessageBus) extends FormulaComponent[PSpyDataReport](eventBus) with IdlePowerConfiguration with Configuration { + import org.powerapi.module.PowerChannel.publishPowerReport + import org.powerapi.module.PowerUnit + import org.powerapi.module.powerspy.PSpyMetricsChannel.{PSpyDataReport, PSpyAllDataReport, PSpyRatioDataReport, subscribePSpyAllDataReport,subscribePSpyRatioDataReport} + + def subscribeSensorReport(): Unit = { + subscribePSpyAllDataReport(eventBus)(self) + subscribePSpyRatioDataReport(eventBus)(self) + } + + def compute(sensorReport: PSpyDataReport): Unit = { + lazy val power: Double = sensorReport match { + case msg: PSpyAllDataReport => msg.rms * msg.uScale * msg.iScale + case msg: PSpyRatioDataReport => (msg.rms * msg.uScale * msg.iScale - idlePower) * msg.targetRatio.ratio + } + + publishPowerReport(sensorReport.muid, sensorReport.target, power, PowerUnit.W, "powerspy", sensorReport.tick)(eventBus) + } +} diff --git a/src/main/scala/org/powerapi/module/powerspy/PowerSpySensor.scala b/src/main/scala/org/powerapi/module/powerspy/PowerSpySensor.scala new file mode 100644 index 0000000..92c2d9b --- /dev/null +++ b/src/main/scala/org/powerapi/module/powerspy/PowerSpySensor.scala @@ -0,0 +1,108 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy + +import akka.util.Timeout +import org.powerapi.core.{OSHelper, MessageBus} +import org.powerapi.module.SensorComponent + +trait Configuration extends org.powerapi.core.Configuration { + import org.powerapi.core.ConfigValue + + lazy val sppUrl = load { _.getString("powerapi.powerspy.spp-url") } match { + case ConfigValue(url) => url + case _ => "btspp://nothing" + } + + lazy val version = load { _.getInt("powerapi.powerspy.version") } match { + case ConfigValue(2) => PowerSpyVersion.POWERSPY_V2 + case _ => PowerSpyVersion.POWERSPY_V1 + } +} + +class PowerSpySensor(eventBus: MessageBus, osHelper: OSHelper, timeout: Timeout) extends SensorComponent(eventBus) with Configuration { + import akka.actor.{Actor, ActorRef} + import akka.event.LoggingReceive + import akka.pattern.gracefulStop + import org.powerapi.core.MonitorChannel.MonitorTick + import org.powerapi.core.{Process, TargetUsageRatio} + import org.powerapi.module.powerspy.PSpyMetricsChannel.{PSpyChildMessage, PSpyStart, publishPSpyDataReport} + + override def postStop() = { + gracefulStop(pspyChild, timeout.duration) + super.postStop() + } + + lazy val childProps = PowerSpyChild.props(sppUrl, version) + lazy val pspyChild: ActorRef = childProps match { + case Some(props) => context.actorOf(props, "pspy-child") + case _ => log.error("the PowerSpy ({}) is not reachable", sppUrl); null + } + + /** + * The default behavior is overridden because this sensor handles other messages + * and it has different states. + */ + override def receive: PartialFunction[Any, Unit] = LoggingReceive { + case msg: MonitorTick => start() + } orElse default + + def start(): Unit = { + pspyChild ! PSpyStart + context.become(running(List())) + } + + def running(messages: List[PSpyChildMessage]): Actor.Receive = LoggingReceive { + case msg: MonitorTick => sense(msg, messages) + case msg: PSpyChildMessage => context.become(running(messages :+ msg)) + } orElse default + + def sense(monitorTick: MonitorTick, messages: List[PSpyChildMessage]): Unit = { + PSpyChildMessage.avg(messages) match { + case Some(avg) => { + monitorTick.target match { + case process: Process => { + lazy val processTime = osHelper.getProcessCpuTime(process) match { + case Some(time) => time + case _ => 0 + } + lazy val globalTime = osHelper.getGlobalCpuTime() match { + case Some(time) => time + case _ => 1 // we cannot divide by 0 + } + lazy val processUsage = TargetUsageRatio(processTime / globalTime) + publishPSpyDataReport(monitorTick.muid, monitorTick.target, processUsage, avg.rms, avg.uScale, avg.iScale, monitorTick.tick)(eventBus) + } + } + + context.become(running(List())) + } + case _ => log.debug("no powerspy messages received") + } + } + + /** + * Not used here, the default behavior is overridden. + */ + def sense(monitorTick: MonitorTick): Unit = {} +} diff --git a/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala b/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala index 1d0479b..af22ca2 100644 --- a/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala +++ b/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala @@ -26,7 +26,7 @@ import java.util.UUID import akka.actor.ActorRef import org.powerapi.core.ClockChannel.ClockTick -import org.powerapi.core.{MessageBus, Target, TimeInStates} +import org.powerapi.core.{TargetUsageRatio, MessageBus, Target, TimeInStates} import org.powerapi.module.{SensorReport, SensorChannel} /** @@ -36,10 +36,6 @@ import org.powerapi.module.{SensorReport, SensorChannel} */ object ProcMetricsChannel extends SensorChannel { - /** - * Wrapper classes. - */ - case class TargetUsageRatio(ratio: Double = 0) case class CacheKey(muid: UUID, target: Target) /** diff --git a/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala b/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala index f9aa0df..5465fd6 100644 --- a/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala +++ b/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala @@ -44,7 +44,7 @@ class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponen * and providing the target CPU ratio usage. */ class TargetRatio { - import org.powerapi.core.{All, Application, Process} + import org.powerapi.core.{All, Application, Process, TargetUsageRatio} import ProcMetricsChannel.CacheKey /** @@ -80,12 +80,12 @@ class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponen } lazy val globalTime: Long = osHelper.getGlobalCpuTime() match { case Some(value) => value - case _ => 0l + case _ => 1 // we cannot divide by 0 } (processTime, globalTime) } - def handleMonitorTick(tick: MonitorTick): ProcMetricsChannel.TargetUsageRatio = { + def handleMonitorTick(tick: MonitorTick): TargetUsageRatio = { val now = tick.target match { case process: Process => handleProcessTarget(process) case application: Application => handleApplicationTarget(application) @@ -98,10 +98,10 @@ class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponen val globalDiff = now._2 - old._2 if (globalDiff <= 0) { - ProcMetricsChannel.TargetUsageRatio(0) + TargetUsageRatio(0) } else { - ProcMetricsChannel.TargetUsageRatio((now._1 - old._1).doubleValue / globalDiff) + TargetUsageRatio((now._1 - old._1).doubleValue / globalDiff) } } } diff --git a/src/test/scala/org/powerapi/module/FormulaSuite.scala b/src/test/scala/org/powerapi/module/FormulaSuite.scala index b57c47f..8157de0 100644 --- a/src/test/scala/org/powerapi/module/FormulaSuite.scala +++ b/src/test/scala/org/powerapi/module/FormulaSuite.scala @@ -27,20 +27,23 @@ import java.util.UUID import akka.actor.{ActorRef, ActorSystem, Props} import akka.testkit.{TestActorRef, TestKit} import org.powerapi.UnitTest -import org.powerapi.core.{Channel, MessageBus} +import org.powerapi.core.MessageBus import org.powerapi.module.SensorMockChannel.SensorMockReport object SensorMockChannel extends SensorChannel { + import org.powerapi.core.ClockChannel.ClockTick + import org.powerapi.core.Target + private val topic = "test" - case class SensorMockReport(topic: String, muid: UUID, power: Double) extends SensorReport + case class SensorMockReport(topic: String, muid: UUID, target: Target, power: Double, tick: ClockTick) extends SensorReport def subscribeMockMessage: MessageBus => ActorRef => Unit = { subscribe(topic) } - def publishSensorMockReport(muid: UUID, power: Double): MessageBus => Unit = { - publish(SensorMockReport(topic, muid, power)) + def publishSensorMockReport(muid: UUID, target: Target, power: Double, tick: ClockTick): MessageBus => Unit = { + publish(SensorMockReport(topic, muid, target, power, tick)) } } @@ -69,15 +72,20 @@ class FormulaSuite(system: ActorSystem) extends UnitTest(system) { } "A Formula" should "process SensorReport messages" in new Bus { + import org.powerapi.core.ClockChannel.ClockTick + import org.powerapi.core.Process import org.powerapi.module.SensorMockChannel.publishSensorMockReport + import scala.concurrent.duration.DurationInt val coeff = 10d val formulaMock = TestActorRef(Props(classOf[FormulaMock], eventBus, testActor, coeff))(system) val muid = UUID.randomUUID() val power = 2.2d + val target = Process(1) + val tick = ClockTick("test", 25.milliseconds) - publishSensorMockReport(muid, power)(eventBus) + publishSensorMockReport(muid, target, power, tick)(eventBus) expectMsgClass(classOf[Double]) should equal(power * coeff) } } diff --git a/src/test/scala/org/powerapi/module/powerspy/PowerSpyFormulaSuite.scala b/src/test/scala/org/powerapi/module/powerspy/PowerSpyFormulaSuite.scala new file mode 100644 index 0000000..151657a --- /dev/null +++ b/src/test/scala/org/powerapi/module/powerspy/PowerSpyFormulaSuite.scala @@ -0,0 +1,81 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy + +import java.util.UUID + +import akka.actor.{Props, ActorSystem} +import akka.testkit.{TestActorRef, TestKit} +import akka.util.Timeout +import org.powerapi.UnitTest +import org.powerapi.core.MessageBus +import scala.concurrent.duration.DurationInt + +class PowerSpyFormulaMock(eventBus: MessageBus) extends PowerSpyFormula(eventBus) { + override lazy val idlePower = 87.50 +} + +class PowerSpyFormulaSuite(system: ActorSystem) extends UnitTest(system) { + + implicit val timeout = Timeout(1.seconds) + + def this() = this(ActorSystem("PowerSpyFormulaSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + val eventBus = new MessageBus + val formulaMock = TestActorRef(Props(classOf[PowerSpyFormulaMock], eventBus), "powerspyFormula")(system) + + "A PowerSpyFormula" should "compute the power when receiving a PspyDataReport" in { + import org.powerapi.core.{All, Process, TargetUsageRatio} + import org.powerapi.core.ClockChannel.ClockTick + import org.powerapi.module.PowerChannel.{PowerReport, subscribePowerReport} + import org.powerapi.module.PowerUnit + import PSpyMetricsChannel.publishPSpyDataReport + + val muid = UUID.randomUUID() + val rms = 2000000d + val u = 0.080f + val i = 5.7E-4f + val ratio = TargetUsageRatio(0.80) + val allPower = rms * u * i + val processPower = (rms * u * i - 87.50) * ratio.ratio + val tickMock = ClockTick("test", 1.seconds) + + subscribePowerReport(muid)(eventBus)(testActor) + + publishPSpyDataReport(muid, All, rms, u, i, tickMock)(eventBus) + expectMsgClass(classOf[PowerReport]) match { + case PowerReport(_, id, All, pow, PowerUnit.W, "powerspy", tic) if muid == id && allPower == pow && tickMock == tic => assert(true) + case _ => assert(false) + } + + publishPSpyDataReport(muid, Process(1), ratio, rms, u, i, tickMock)(eventBus) + expectMsgClass(classOf[PowerReport]) match { + case PowerReport(_, id, pr, pow, PowerUnit.W, "powerspy", tic) if muid == id && Process(1) == pr && processPower == pow && tickMock == tic => assert(true) + case _ => assert(false) + } + } +} diff --git a/src/test/scala/org/powerapi/module/powerspy/PowerSpySensorSuite.scala b/src/test/scala/org/powerapi/module/powerspy/PowerSpySensorSuite.scala new file mode 100644 index 0000000..2886433 --- /dev/null +++ b/src/test/scala/org/powerapi/module/powerspy/PowerSpySensorSuite.scala @@ -0,0 +1,148 @@ +/* + * This software is licensed under the GNU Affero General Public License, quoted below. + * + * This file is a part of PowerAPI. + * + * Copyright (C) 2011-2014 Inria, University of Lille 1. + * + * PowerAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * PowerAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with PowerAPI. + * + * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. + */ +package org.powerapi.module.powerspy + +import java.util.UUID + +import akka.actor.{Actor, Props, ActorSystem} +import akka.testkit.{TestActorRef, TestKit} +import akka.util.Timeout +import org.powerapi.UnitTest +import org.powerapi.core.{OSHelper, MessageBus} +import scala.concurrent.duration.DurationInt + +class PSpyDataListener(eventBus: MessageBus, muid: UUID) extends Actor { + import PSpyMetricsChannel.{PSpyDataReport, subscribePSpyAllDataReport, subscribePSpyRatioDataReport} + + override def preStart(): Unit = { + subscribePSpyAllDataReport(eventBus)(self) + subscribePSpyRatioDataReport(eventBus)(self) + } + + def receive() = { + case msg: PSpyDataReport => println(msg) + } +} + +class PowerSpySensorMock(eventBus: MessageBus, osHelper: OSHelper, timeout: Timeout) + extends PowerSpySensor(eventBus, osHelper, timeout) { + + override lazy val sppUrl = "btspp://000BCE071E9B:1;authenticate=false;encrypt=false;master=false" + override lazy val version = PowerSpyVersion.POWERSPY_V1 +} + +class OSHelperMock extends OSHelper { + import org.powerapi.core.{Application, Process, Thread, TimeInStates} + + def getProcesses(application: Application): List[Process] = List(Process(2), Process(3)) + + def getThreads(process: Process): List[Thread] = List() + + def getProcessCpuTime(process: Process): Option[Long] = { + process match { + case Process(2) => Some(10 + 5) + case Process(3) => Some(3 + 5) + case _ => None + } + } + + def getGlobalCpuTime(): Option[Long] = Some(43171 + 1 + 24917 + 25883594 + 1160 + 19 + 1477 + 0) + + def getTimeInStates(): TimeInStates = TimeInStates(Map()) +} + +class PowerSpySensorSuite(system: ActorSystem) extends UnitTest(system) { + import PSpyMetricsChannel.PSpyChildMessage + + implicit val timeout = Timeout(1.seconds) + + def this() = this(ActorSystem("PowerSpySensorSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + "A PSpyChildMessage" can "be summed with another one" in { + PSpyChildMessage(3.0, 4.0f, 1.0f) + PSpyChildMessage(4.0, 5.0f, 7.0f) should equal(PSpyChildMessage(7.0, 9.0f, 8.0f)) + } + + it can "be divided with another one" in { + PSpyChildMessage(3.0, 6.0f, 9.0f) / 2 match { + case Some(msg) if PSpyChildMessage(3.0 / 2, 6.0f / 2, 9.0f / 2) == msg => assert(true) + case _ => assert(false) + } + + PSpyChildMessage(3.0, 3.0f, 3.0f) / 0 match { + case None => assert(true) + case _ => assert(false) + } + } + + "An average of a PSpyChildMessage messages" can "be computed" in { + PSpyChildMessage.avg(List(PSpyChildMessage(3.0, 6.0f, 9.0f), PSpyChildMessage(1.0, 4.0F, 8.0f))) match { + case Some(msg) if PSpyChildMessage(4.0 / 2, 10.0f / 2, 17.0f / 2) == msg => assert(true) + case _ => assert(false) + } + + PSpyChildMessage.avg(List()) match { + case None => assert(true) + case _ => assert(false) + } + } + + "A PowerSpySensor" should "open the connection with the power meter, collect the data and then produce PSpyRatioDataReport messages when the target is Process/Application" ignore { + import org.powerapi.core.{Application, Clocks, Monitors} + import org.powerapi.core.MonitorChannel.startMonitor + import akka.pattern.gracefulStop + + val eventBus = new MessageBus + val muid = UUID.randomUUID() + TestActorRef(Props(classOf[Clocks], eventBus), "clocks")(system) + TestActorRef(Props(classOf[Monitors], eventBus), "monitors")(system) + val pspySensor = TestActorRef(Props(classOf[PowerSpySensorMock], eventBus, new OSHelperMock, 20.seconds), "pspySensor")(system) + TestActorRef(Props(classOf[PSpyDataListener], eventBus, muid), "pspyListener")(system) + + startMonitor(muid, 1.seconds, List(Application("app")))(eventBus) + Thread.sleep(20.seconds.toMillis) + + gracefulStop(pspySensor, 20.seconds) + } + + it should "open the connection with the power meter, collect the data and then produce PSpyAllDataReport messages when the target is All" ignore { + import org.powerapi.core.{All, Clocks, Monitors} + import org.powerapi.core.MonitorChannel.startMonitor + import akka.pattern.gracefulStop + + val eventBus = new MessageBus + val muid = UUID.randomUUID() + TestActorRef(Props(classOf[Clocks], eventBus), "clocks")(system) + TestActorRef(Props(classOf[Monitors], eventBus), "monitors")(system) + val pspySensor = TestActorRef(Props(classOf[PowerSpySensorMock], eventBus, new OSHelperMock, 20.seconds), "pspySensor")(system) + TestActorRef(Props(classOf[PSpyDataListener], eventBus, muid), "pspyListener")(system) + + startMonitor(muid, 1.seconds, List(All))(eventBus) + Thread.sleep(20.seconds.toMillis) + + gracefulStop(pspySensor, 20.seconds) + } +} diff --git a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala index 03871a8..0073328 100644 --- a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala +++ b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala @@ -30,7 +30,6 @@ import akka.util.Timeout import org.powerapi.UnitTest import org.powerapi.core.MessageBus import org.powerapi.module.PowerChannel -import org.powerapi.module.procfs.ProcMetricsChannel import scala.concurrent.duration.DurationInt trait DvfsCpuFormulaConfigurationMock extends FormulaConfiguration { @@ -73,9 +72,9 @@ class DvfsCpuFormulaSuite(system: ActorSystem) extends UnitTest(system) { } it should "compute correctly the process' power" in { - import org.powerapi.core.{Process,TimeInStates} + import org.powerapi.core.{Process, TargetUsageRatio, TimeInStates} import org.powerapi.core.ClockChannel.ClockTick - import ProcMetricsChannel.{UsageReport, TargetUsageRatio} + import org.powerapi.module.procfs.ProcMetricsChannel.UsageReport val topic = "test" val muid = UUID.randomUUID() @@ -98,10 +97,10 @@ class DvfsCpuFormulaSuite(system: ActorSystem) extends UnitTest(system) { } it should "process a SensorReport and then publish a PowerReport" in { - import org.powerapi.core.{Process,TimeInStates} + import org.powerapi.core.{Process, TargetUsageRatio, TimeInStates} import org.powerapi.core.ClockChannel.ClockTick import PowerChannel.{PowerReport, subscribePowerReport} - import ProcMetricsChannel.{publishUsageReport, TargetUsageRatio} + import org.powerapi.module.procfs.ProcMetricsChannel.publishUsageReport import org.powerapi.module.PowerUnit val muid = UUID.randomUUID() diff --git a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuFormulaSuite.scala b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuFormulaSuite.scala index 224c518..29893ea 100644 --- a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuFormulaSuite.scala +++ b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuFormulaSuite.scala @@ -28,8 +28,6 @@ import akka.testkit.{TestActorRef, TestKit} import akka.util.Timeout import org.powerapi.UnitTest import org.powerapi.core.MessageBus -import org.powerapi.module.PowerChannel -import org.powerapi.module.procfs.ProcMetricsChannel import scala.concurrent.duration.DurationInt trait SimpleCpuFormulaConfigurationMock extends FormulaConfiguration { @@ -55,10 +53,10 @@ class SimpleCpuFormulaSuite(system: ActorSystem) extends UnitTest(system) { val formulaMock = TestActorRef(Props(classOf[SimpleCpuFormulaMock], eventBus), "simple-cpuFormula")(system) "A simple cpu formula" should "process a SensorReport and then publish a PowerReport" in { - import org.powerapi.core.Process + import org.powerapi.core.{Process, TargetUsageRatio} import org.powerapi.core.ClockChannel.ClockTick - import PowerChannel.{PowerReport, subscribePowerReport} - import ProcMetricsChannel.{publishUsageReport, TargetUsageRatio} + import org.powerapi.module.PowerChannel.{PowerReport, subscribePowerReport} + import org.powerapi.module.procfs.ProcMetricsChannel.publishUsageReport import org.powerapi.module.PowerUnit val muid = UUID.randomUUID() diff --git a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala index ba81692..444e8f4 100644 --- a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala +++ b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala @@ -54,10 +54,10 @@ class OSHelperMock extends OSHelper { } class SimpleCpuSensorSuite(system: ActorSystem) extends UnitTest(system) { + import org.powerapi.core.{All, Application, Process, TargetUsageRatio} import org.powerapi.core.ClockChannel.ClockTick import org.powerapi.core.MonitorChannel.MonitorTick - import org.powerapi.core.{All, Application, Process} - import org.powerapi.module.procfs.ProcMetricsChannel.{CacheKey, UsageReport, TargetUsageRatio} + import org.powerapi.module.procfs.ProcMetricsChannel.{CacheKey, UsageReport} implicit val timeout = Timeout(1.seconds)