diff --git a/README.md b/README.md index 2aebbfb..3cbc71a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ When submitting code, please make every effort to follow existing conventions an We all stand on the shoulders of giants and get by with a little help from our friends. PowerAPI is written in [Scala](http://www.scala-lang.org) (version 2.11.4 under [3-clause BSD license](http://www.scala-lang.org/license.html)) and built on top of: * [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. # Licence This software is licensed under the *GNU Affero General Public License*, quoted below. diff --git a/build.sbt b/build.sbt index 1ae1cf4..3d4c271 100644 --- a/build.sbt +++ b/build.sbt @@ -7,6 +7,8 @@ scalaVersion := "2.11.4" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % "2.3.6", "com.typesafe" % "config" % "1.2.1", + "org.apache.logging.log4j" % "log4j-api" % "2.1", + "org.apache.logging.log4j" % "log4j-core" % "2.1", "com.typesafe.akka" %% "akka-testkit" % "2.3.6" % "test", "org.scalatest" %% "scalatest" % "2.2.2" % "test" ) diff --git a/src/main/scala/org/powerapi/configuration/LogicalCoresConfiguration.scala b/src/main/scala/org/powerapi/configuration/LogicalCoresConfiguration.scala new file mode 100644 index 0000000..bdb100c --- /dev/null +++ b/src/main/scala/org/powerapi/configuration/LogicalCoresConfiguration.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 + +/** + * Number of logical cores / Configuration. + * + * @author Maxime Colmant + */ +trait LogicalCoresConfiguration { + self: Configuration => + + import org.powerapi.core.ConfigValue + + lazy val cores = load { _.getInt("powerapi.hardware.cores") } match { + case ConfigValue(nbCores) => nbCores + case _ => 0 + } +} diff --git a/src/main/scala/org/powerapi/core/ClockActors.scala b/src/main/scala/org/powerapi/core/ClockActors.scala index 89b6b37..67495ef 100644 --- a/src/main/scala/org/powerapi/core/ClockActors.scala +++ b/src/main/scala/org/powerapi/core/ClockActors.scala @@ -25,15 +25,16 @@ package org.powerapi.core import akka.actor.SupervisorStrategy.{Directive, Resume} import akka.actor.{Actor, Cancellable, PoisonPill, Props} import akka.event.LoggingReceive - import scala.concurrent.duration.{Duration, FiniteDuration} /** * One child clock is created per frequency. * Allows to publish a message in the right topics for a given frequency. + * + * @author Maxime Colmant */ -class ClockChild(eventBus: MessageBus, frequency: FiniteDuration) extends Component { - import org.powerapi.core.ClockChannel.{ClockStart, ClockStop, ClockStopAll, publishTick} +class ClockChild(eventBus: MessageBus, frequency: FiniteDuration) extends ActorComponent { + import org.powerapi.core.ClockChannel.{ClockStart, ClockStop, ClockStopAll, publishClockTick} def receive: PartialFunction[Any, Unit] = LoggingReceive { case ClockStart(_, freq) if frequency == freq => start() @@ -60,7 +61,7 @@ class ClockChild(eventBus: MessageBus, frequency: FiniteDuration) extends Compon */ def start(): Unit = { val timer = context.system.scheduler.schedule(Duration.Zero, frequency) { - publishTick(frequency)(eventBus) + publishClockTick(frequency)(eventBus) } (context.system.dispatcher) log.info("clock started, reference: {}", frequency.toNanos) @@ -89,12 +90,14 @@ class ClockChild(eventBus: MessageBus, frequency: FiniteDuration) extends Compon /** * This clock listens the bus on a given topic and reacts on the received message. * It is responsible to handle a pool of clocks for the monitored frequencies. + * + * @author Maxime Colmant */ -class Clocks(eventBus: MessageBus) extends Component with Supervisor { - import org.powerapi.core.ClockChannel.{ClockStart, ClockStop, ClockStopAll, formatClockChildName, stopAllClock, subscribeTickSubscription} +class Clocks(eventBus: MessageBus) extends Supervisor { + import org.powerapi.core.ClockChannel.{ClockStart, ClockStop, ClockStopAll, formatClockChildName, stopAllClock, subscribeClockChannel} override def preStart(): Unit = { - subscribeTickSubscription(eventBus)(self) + subscribeClockChannel(eventBus)(self) } override def postStop(): Unit = { diff --git a/src/main/scala/org/powerapi/core/ClockChannel.scala b/src/main/scala/org/powerapi/core/ClockChannel.scala index d73ba3f..7f506cc 100644 --- a/src/main/scala/org/powerapi/core/ClockChannel.scala +++ b/src/main/scala/org/powerapi/core/ClockChannel.scala @@ -20,16 +20,15 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core import akka.actor.ActorRef - import scala.concurrent.duration.FiniteDuration - /** * Clock channel and messages. + * + * @author Maxime Colmant */ object ClockChannel extends Channel { @@ -47,15 +46,6 @@ object ClockChannel extends Channel { frequency: FiniteDuration, timestamp: Long = System.currentTimeMillis) extends ClockMessage - /** - * ClockTickSubscription is represented as a dedicated type of message. - * - * @param topic: subject used for routing the message. - * @param frequency: clock frequency. - */ - case class ClockTickSubscription(topic: String, - frequency: FiniteDuration) extends ClockMessage - /** * ClockStart is represented as a dedicated type of message. * @@ -82,17 +72,17 @@ object ClockChannel extends Channel { /** * Topic for communicating with the Clock. */ - private val topic = "tick:subscription" + private val topic = "clock:handling" /** * External methods used by the Monitor actors to subscribe/unsubscribe, * start/stop a clock which runs at a frequency. */ - def subscribeClock(frequency: FiniteDuration): (MessageBus => ActorRef => Unit) = { + def subscribeClockTick(frequency: FiniteDuration): MessageBus => ActorRef => Unit = { subscribe(clockTickTopic(frequency)) } - def unsubscribeClock(frequency: FiniteDuration): MessageBus => ActorRef => Unit = { + def unsubscribeClockTick(frequency: FiniteDuration): MessageBus => ActorRef => Unit = { unsubscribe(clockTickTopic(frequency)) } @@ -107,7 +97,7 @@ object ClockChannel extends Channel { /** * Internal methods used by the Clocks actor for interacting with the bus. */ - def subscribeTickSubscription: MessageBus => ActorRef => Unit = { + def subscribeClockChannel: MessageBus => ActorRef => Unit = { subscribe(topic) } @@ -116,7 +106,7 @@ object ClockChannel extends Channel { /** * Internal methods used by the ClockChild actors for interacting with the bus. */ - def publishTick(frequency: FiniteDuration): MessageBus => Unit = { + def publishClockTick(frequency: FiniteDuration): MessageBus => Unit = { publish(ClockTick(clockTickTopic(frequency), frequency)) } diff --git a/src/main/scala/org/powerapi/core/Component.scala b/src/main/scala/org/powerapi/core/Component.scala index 1c115f2..7e114a1 100644 --- a/src/main/scala/org/powerapi/core/Component.scala +++ b/src/main/scala/org/powerapi/core/Component.scala @@ -20,18 +20,19 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core +import akka.actor.{OneForOneStrategy, SupervisorStrategy, SupervisorStrategyConfigurator, ActorLogging, Actor} import akka.actor.SupervisorStrategy.{Directive, Resume} -import akka.actor.{Actor, ActorLogging, OneForOneStrategy, SupervisorStrategy, SupervisorStrategyConfigurator} - +import akka.event.LoggingReceive import scala.concurrent.duration.DurationInt /** * Base trait for components which use Actor. + * + * @author Maxime Colmant */ -trait Component extends Actor with ActorLogging { +trait ActorComponent extends Actor with ActorLogging { /** * Default behavior when a received message is unknown. */ @@ -40,10 +41,19 @@ trait Component extends Actor with ActorLogging { } } +/** + * Base trait for API component. + * + * @author Maxime Colmant + */ +trait APIComponent extends ActorComponent + /** * Supervisor strategy. + * + * @author Maxime Colmant */ -trait Supervisor extends Component { +trait Supervisor extends ActorComponent { def handleFailure: PartialFunction[Throwable, Directive] override def supervisorStrategy: SupervisorStrategy = @@ -53,6 +63,8 @@ trait Supervisor extends Component { /** * This class is used for defining a default supervisor strategy for the Guardian Actor. * The Guardian Actor is the main actor used when system.actorOf(...) is used. + * + * @author Maxime Colmant */ class GuardianFailureStrategy extends SupervisorStrategyConfigurator { def handleFailure: PartialFunction[Throwable, Directive] = { diff --git a/src/main/scala/org/powerapi/core/Configuration.scala b/src/main/scala/org/powerapi/core/Configuration.scala index 6904d21..49570f5 100644 --- a/src/main/scala/org/powerapi/core/Configuration.scala +++ b/src/main/scala/org/powerapi/core/Configuration.scala @@ -24,7 +24,6 @@ package org.powerapi.core import com.typesafe.config.{Config, ConfigException, ConfigFactory} - /** * Base trait for configuration result. */ @@ -32,12 +31,16 @@ trait ConfigResult[T] /** * Subtypes to specify the different types of result. + * + * @author Maxime Colmant */ case class ConfigValue[T](value: T) extends ConfigResult[T] case class ConfigError[T](exception: Throwable) extends ConfigResult[T] /** * Base trait for dealing with configuration files. + * + * @author Maxime Colmant */ trait Configuration { private lazy val conf = ConfigFactory.load() @@ -47,7 +50,7 @@ trait Configuration { * * @param request: request for getting information. */ - def load[T](request: Config => T): ConfigResult[T] = { + protected def load[T](request: Config => T): ConfigResult[T] = { try { ConfigValue(request(conf)) } diff --git a/src/main/scala/org/powerapi/core/FileHelper.scala b/src/main/scala/org/powerapi/core/FileHelper.scala new file mode 100644 index 0000000..461e25d --- /dev/null +++ b/src/main/scala/org/powerapi/core/FileHelper.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.core + +/** + * Implement the Loan's pattern for closing automatically a resource. + * + * @see https://wiki.scala-lang.org/display/SYGN/Loan + * + * @author Maxime Colmant + */ +object FileHelper { + def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B = { + try { + f(resource) + } + finally { + resource.close() + } + } +} diff --git a/src/main/scala/org/powerapi/core/MessageBus.scala b/src/main/scala/org/powerapi/core/MessageBus.scala index c705200..a363625 100644 --- a/src/main/scala/org/powerapi/core/MessageBus.scala +++ b/src/main/scala/org/powerapi/core/MessageBus.scala @@ -20,17 +20,16 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core -import java.util.UUID - import akka.actor.ActorRef import akka.event.LookupClassification - /** * Messages are the messages used to route the messages in the bus. + * + * @author Romain Rouvoy + * @author Maxime Colmant */ trait Message { /** @@ -40,15 +39,10 @@ trait Message { } /** - * Reports are the base messages exchanged between PowerAPI components. + * Main types definition. + * + * @author Maxime Colmant */ -trait Report extends Message { - /** - * A report is associated with a monitor unique identifier (MUID), which is at the origin of the report flow. - */ - def muid: UUID -} - trait EventBus extends akka.event.EventBus { type Event = Message type Classifier = String @@ -57,6 +51,9 @@ trait EventBus extends akka.event.EventBus { /** * Common event bus used by PowerAPI components to communicate. + * + * @author Loic Huertas + * @author Maxime Colmant */ class MessageBus extends EventBus with LookupClassification { // is used for extracting the classifier from the incoming events @@ -80,19 +77,22 @@ class MessageBus extends EventBus with LookupClassification { /** * Used to specify the channels used by the components. + * + * @author Romain Rouvoy + * @author Maxime Colmant */ class Channel { type M <: Message - def subscribe(topic: String)(bus: EventBus)(subscriber: ActorRef): Unit = { + protected def subscribe(topic: String)(bus: EventBus)(subscriber: ActorRef): Unit = { bus.subscribe(subscriber, topic) } - def unsubscribe(topic: String)(bus: EventBus)(subscriber: ActorRef): Unit = { + protected def unsubscribe(topic: String)(bus: EventBus)(subscriber: ActorRef): Unit = { bus.unsubscribe(subscriber, topic) } - def publish(message: M)(bus: EventBus): Unit = { + protected def publish(message: M)(bus: EventBus): Unit = { bus.publish(message) } } diff --git a/src/main/scala/org/powerapi/core/MonitorActors.scala b/src/main/scala/org/powerapi/core/MonitorActors.scala index adbcb8e..c5cfa95 100644 --- a/src/main/scala/org/powerapi/core/MonitorActors.scala +++ b/src/main/scala/org/powerapi/core/MonitorActors.scala @@ -23,25 +23,25 @@ package org.powerapi.core import java.util.UUID - import akka.actor.SupervisorStrategy.{Directive, Resume} import akka.actor.{Actor, PoisonPill, Props} import akka.event.LoggingReceive - +import org.powerapi.core.ClockChannel.ClockTick import scala.concurrent.duration.FiniteDuration - /** * One child represents one monitor. * Allows to publish messages in the right topics depending of the targets. + * + * @author Maxime Colmant */ class MonitorChild(eventBus: MessageBus, muid: UUID, frequency: FiniteDuration, - targets: List[Target]) extends Component { + targets: List[Target]) extends ActorComponent { - import org.powerapi.core.ClockChannel.{ClockTick, startClock, stopClock, subscribeClock, unsubscribeClock} - import org.powerapi.core.MonitorChannel.{MonitorStart, MonitorStop, MonitorStopAll, publishTarget} + import org.powerapi.core.ClockChannel.{startClock, stopClock, subscribeClockTick, unsubscribeClockTick} + import org.powerapi.core.MonitorChannel.{MonitorStart, MonitorStop, MonitorStopAll, publishMonitorTick} def receive: PartialFunction[Any, Unit] = LoggingReceive { case MonitorStart(_, id, freq, targs) if muid == id && frequency == freq && targets == targs => start() @@ -51,7 +51,7 @@ class MonitorChild(eventBus: MessageBus, * Running state. */ def running: Actor.Receive = LoggingReceive { - case _: ClockTick => produceMessages() + case tick: ClockTick => produceMessages(tick) case MonitorStop(_, id) if muid == id => stop() case _: MonitorStopAll => stop() } orElse default @@ -61,16 +61,16 @@ class MonitorChild(eventBus: MessageBus, */ def start(): Unit = { startClock(frequency)(eventBus) - subscribeClock(frequency)(eventBus)(self) + subscribeClockTick(frequency)(eventBus)(self) log.info("monitor is started, muid: {}", muid) context.become(running) } /** - * Handle ticks for publishing the targets in the right topics. + * Handle ticks for publishing the targets in the right topic. */ - def produceMessages(): Unit = { - targets.foreach(target => publishTarget(muid, target)(eventBus)) + def produceMessages(tick: ClockTick): Unit = { + targets.foreach(target => publishMonitorTick(muid, target, tick)(eventBus)) } /** @@ -79,7 +79,7 @@ class MonitorChild(eventBus: MessageBus, */ def stop(): Unit = { stopClock(frequency)(eventBus) - unsubscribeClock(frequency)(eventBus)(self) + unsubscribeClockTick(frequency)(eventBus)(self) log.info("monitor is stopped, muid: {}", muid) self ! PoisonPill } @@ -88,12 +88,14 @@ class MonitorChild(eventBus: MessageBus, /** * This actor listens the bus on a given topic and reacts on the received messages. * It is responsible to handle a pool of child actors which represent all monitors. + * + * @author Maxime Colmant */ -class Monitors(eventBus: MessageBus) extends Component with Supervisor { - import org.powerapi.core.MonitorChannel.{MonitorStart, MonitorStop, MonitorStopAll, formatMonitorChildName, stopAllMonitor, subscribeHandlingMonitor} +class Monitors(eventBus: MessageBus) extends Supervisor { + import org.powerapi.core.MonitorChannel.{MonitorStart, MonitorStop, MonitorStopAll, formatMonitorChildName, stopAllMonitor, subscribeMonitorsChannel} override def preStart(): Unit = { - subscribeHandlingMonitor(eventBus)(self) + subscribeMonitorsChannel(eventBus)(self) } override def postStop(): Unit = { diff --git a/src/main/scala/org/powerapi/core/MonitorChannel.scala b/src/main/scala/org/powerapi/core/MonitorChannel.scala index d69cd66..a67ae44 100644 --- a/src/main/scala/org/powerapi/core/MonitorChannel.scala +++ b/src/main/scala/org/powerapi/core/MonitorChannel.scala @@ -20,17 +20,17 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core import java.util.UUID - import akka.actor.ActorRef - +import org.powerapi.core.ClockChannel.ClockTick import scala.concurrent.duration.FiniteDuration /** * Monitor channel and messages. + * + * @author Maxime Colmant */ object MonitorChannel extends Channel { @@ -39,15 +39,17 @@ object MonitorChannel extends Channel { trait MonitorMessage extends Message /** - * MonitorTarget is represented as a dedicated type of message. + * MonitorTick 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 tick: tick origin. */ - case class MonitorTarget(topic: String, - muid: UUID, - target: Target) extends MonitorMessage with Report + case class MonitorTick(topic: String, + muid: UUID, + target: Target, + tick: ClockTick) extends MonitorMessage /** * MonitorStart is represented as a dedicated type of message. @@ -90,7 +92,7 @@ object MonitorChannel extends Channel { /** * External methods used by the Sensor actors for interacting with the bus. */ - def subscribeTarget: MessageBus => ActorRef => Unit = { + def subscribeMonitorTick: MessageBus => ActorRef => Unit = { subscribe(topicToPublish) } @@ -108,7 +110,7 @@ object MonitorChannel extends Channel { /** * Internal methods used by the Monitors actor for interacting with the bus. */ - def subscribeHandlingMonitor: MessageBus => ActorRef => Unit = { + def subscribeMonitorsChannel: MessageBus => ActorRef => Unit = { subscribe(topic) } @@ -117,8 +119,8 @@ object MonitorChannel extends Channel { /** * Internal methods used by the MonitorChild actors for interacting with the bus. */ - def publishTarget(muid: UUID, target: Target): MessageBus => Unit = { - publish(MonitorTarget(topicToPublish, muid, target)) + def publishMonitorTick(muid: UUID, target: Target, tick: ClockTick): MessageBus => Unit = { + publish(MonitorTick(topicToPublish, muid, target, tick)) } /** diff --git a/src/main/scala/org/powerapi/core/OSHelper.scala b/src/main/scala/org/powerapi/core/OSHelper.scala new file mode 100644 index 0000000..f987741 --- /dev/null +++ b/src/main/scala/org/powerapi/core/OSHelper.scala @@ -0,0 +1,221 @@ +/** + * 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.core + +import org.powerapi.configuration.LogicalCoresConfiguration + +/** + * This is not a monitoring target. It's an internal wrapper for the Thread IDentifier. + * + * @param tid: thread identifier + * + * @author Maxime Colmant + */ +case class Thread(tid: Long) + +/** + * Wrapper class for the time spent by the cpu in each frequency (if dvfs enabled). + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +case class TimeInStates(times: Map[Long, Long]) { + def -(that: TimeInStates) = + TimeInStates((for ((frequency, time) <- times) yield (frequency, time - that.times.getOrElse(frequency, 0: Long))).toMap) +} + +/** + * Base trait use for implementing os specific methods. + * + * @author Maxime Colmant + */ +trait OSHelper { + /** + * Get the list of processes behind an Application. + * + * @param application: targeted application. + */ + def getProcesses(application: Application): List[Process] + + /** + * Get the list of thread behind a Process. + * + * @param process: targeted process. + */ + def getThreads(process: Process): List[Thread] + + /** + * Get the process execution time on the cpu. + * + * @param process: targeted process + */ + def getProcessCpuTime(process: Process): Option[Long] + + /** + * Get the global execution time for the cpu. + */ + def getGlobalCpuTime(): Option[Long] + + /** + * Get how many time CPU spent under each frequency. + */ + def getTimeInStates(): TimeInStates +} + +/** + * Linux special helper. + π + * @author Maxime Colmant + */ +class LinuxHelper extends OSHelper with Configuration with LogicalCoresConfiguration { + + import java.io.{IOException, File} + import org.apache.logging.log4j.LogManager + import org.powerapi.core.FileHelper.using + import scala.io.Source + import scala.sys.process.stringSeqToProcess + + private val log = LogManager.getLogger + + private val PSFormat = """^\s*(\d+)""".r + private val GlobalStatFormat = """cpu\s+([\d\s]+)""".r + private val TimeInStateFormat = """(\d+)\s+(\d+)""".r + + /** + * This file allows to get all threads associated to one PID with the help of the procfs. + */ + lazy val taskPath = load { _.getString("powerapi.procfs.process-task-task") } match { + case ConfigValue(path) if path.contains("%?pid") => path + case _ => "/proc/%?pid/task" + } + + /** + * Global stat file, giving global information of the system itself. + * Typically presents under /proc/stat. + */ + lazy val globalStatPath = load { _.getString("powerapi.procfs.global-path") } match { + case ConfigValue(path) => path + case _ => "/proc/stat" + } + + /** + * Process stat file, giving information about the process itself. + * Typically presents under /proc/[pid]/stat. + */ + lazy val processStatPath = load { _.getString("powerapi.procfs.process-path") } match { + case ConfigValue(path) if path.contains("%?pid") => path + case _ => "/proc/%?pid/stat" + } + + /** + * Time in state file, giving information about how many time CPU spent under each frequency. + */ + lazy val timeInStatePath = load { _.getString("powerapi.sysfs.timeinstates-path") } match { + case ConfigValue(path) => path + case _ => "/sys/devices/system/cpu/cpu%?index/cpufreq/stats/time_in_state" + } + + def getProcesses(application: Application): List[Process] = { + Seq("ps", "-C", application.name, "-o", "pid", "--no-headers").!!.split("\n").toList.map { + case PSFormat(pid) => Process(pid.toLong) + } + } + + def getThreads(process: Process): List[Thread] = { + val pidDirectory = new File(taskPath.replace("%?pid", s"${process.pid}")) + + if (pidDirectory.exists && pidDirectory.isDirectory) { + /** + * The pid is removed because it corresponds to the main thread. + */ + pidDirectory.listFiles.filter(dir => dir.isDirectory && dir.getName != s"${process.pid}").toList.map(dir => Thread(dir.getName.toLong)) + } + else List() + } + + def getProcessCpuTime(process: Process): Option[Long] = { + try { + using(Source.fromFile(processStatPath.replace("%?pid", s"${process.pid}")))(source => { + log.debug("using {} as a procfs process stat path", processStatPath) + + val statLine = source.getLines.toIndexedSeq(0).split("\\s") + // User time + System time + Some(statLine(13).toLong + statLine(14).toLong) + }) + } + catch { + case ioe: IOException => log.warn("i/o exception: {}", ioe.getMessage); None + } + } + + def getGlobalCpuTime(): Option[Long] = { + try { + using(Source.fromFile(globalStatPath))(source => { + log.debug("using {} as a procfs global stat path", globalStatPath) + + val time = source.getLines.toIndexedSeq(0) match { + /** + * Exclude all guest columns, they are already added in utime column. + * + * @see http://lxr.free-electrons.com/source/kernel/sched/cputime.c#L165 + */ + case GlobalStatFormat(times) => Some( + times.split("\\s").slice(0, 8).foldLeft(0: Long) { + (acc, x) => acc + x.toLong + } + ) + case _ => log.warn("unable to parse line from {}", globalStatPath); None + } + + time + }) + } + catch { + case ioe: IOException => log.warn("i/o exception: {}", ioe.getMessage); None + } + } + + def getTimeInStates(): TimeInStates = { + val result = collection.mutable.HashMap[Long, Long]() + + for(core <- 0 until cores) { + try { + using(Source.fromFile(timeInStatePath.replace("%?index", s"$core")))(source => { + log.debug("using {} as a sysfs timeinstates path", timeInStatePath) + + for(line <- source.getLines) { + line match { + case TimeInStateFormat(freq, t) => result += (freq.toLong -> (t.toLong + (result.getOrElse(freq.toLong, 0l)))) + case _ => log.warn("unable to parse line {} from file {}", line, timeInStatePath) + } + } + }) + } + catch { + case ioe: IOException => log.warn("i/o exception: {}", ioe.getMessage); + } + } + + TimeInStates(result.toMap[Long, Long]) + } +} diff --git a/src/main/scala/org/powerapi/core/Target.scala b/src/main/scala/org/powerapi/core/Target.scala index 1affa7e..ae4e8fd 100644 --- a/src/main/scala/org/powerapi/core/Target.scala +++ b/src/main/scala/org/powerapi/core/Target.scala @@ -20,24 +20,40 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core /** * Targets are system elements that can be monitored by PowerAPI + * + * @author Romain Rouvoy + * @author Maxime Colmant */ trait Target + /** * Monitoring target for a specific Process IDentifier. - * @param pid: process identifier + * + * @param pid: process identifier. + * + * @author Romain Rouvoy + * @author Maxime Colmant */ case class Process(pid: Long) extends Target + /** * Monitoring target for a specific application. + * * @param name: name of the application. + * + * @author Romain Rouvoy + * @author Maxime Colmant */ case class Application(name: String) extends Target + /** * Monitoring target for the whole system. + * + * @author Romain Rouvoy + * @author Maxime Colmant */ object All extends Target diff --git a/src/main/scala/org/powerapi/module/FormulaComponent.scala b/src/main/scala/org/powerapi/module/FormulaComponent.scala new file mode 100644 index 0000000..54200fb --- /dev/null +++ b/src/main/scala/org/powerapi/module/FormulaComponent.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 + +import akka.event.LoggingReceive +import org.powerapi.core.{APIComponent, MessageBus} + +/** + * Base trait for each PowerAPI formula. + * Each of them should react to a SensorReport, compute the power and then publish a PowerReport. + * + * @author Maxime Colmant + */ +abstract class FormulaComponent(eventBus: MessageBus) extends APIComponent { + + type SR <: SensorReport + + override def preStart(): Unit = { + subscribeSensorReport() + } + + def receive: PartialFunction[Any, Unit] = LoggingReceive { + case msg: SR => compute(msg) + } orElse default + + def subscribeSensorReport(): Unit + def compute(sensorReport: SR): Unit +} diff --git a/src/main/scala/org/powerapi/module/PowerChannel.scala b/src/main/scala/org/powerapi/module/PowerChannel.scala new file mode 100644 index 0000000..bbaab73 --- /dev/null +++ b/src/main/scala/org/powerapi/module/PowerChannel.scala @@ -0,0 +1,96 @@ +/** + * 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 + +import java.util.UUID + +import akka.actor.ActorRef +import org.powerapi.core.{MessageBus, Channel} + +/** + * Power units. + * + * @author Romain Rouvoy + * @author Maxime Colmant + */ +object PowerUnit extends Enumeration { + + case class PowerUnit(name: String, description: String) extends Val + + val W = PowerUnit("W", "Watts") + val kW = PowerUnit("kW", "KiloWatts") +} + +/** + * PowerChannel channel and messages. + * + * @author Maxime Colmant + */ +object PowerChannel extends Channel { + import org.powerapi.core.ClockChannel.ClockTick + import org.powerapi.core.{Message, Target} + import org.powerapi.module.PowerUnit.PowerUnit + + type M = PowerReport + + /** + * FormulaReport 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 power: target's power consumption. + * @param unit: power unit. + * @param device: device targeted. + * @param tick: tick origin. + */ + case class PowerReport(topic: String, + muid: UUID, + target: Target, + power: Double, + unit: PowerUnit, + device: String, + tick: ClockTick) extends Message + + /** + * Publish a PowerReport in the event bus. + */ + def publishPowerReport(muid: UUID, target: Target, power: Double, unit: PowerUnit, device: String, tick: ClockTick): MessageBus => Unit = { + publish(PowerReport(powerReportMuid(muid), muid, target, power, unit, device, tick)) + } + + /** + * External method used by the Reporter for interacting with the bus. + */ + def subscribePowerReport(muid: UUID): MessageBus => ActorRef => Unit = { + subscribe(powerReportMuid(muid)) + } + + def unsubscribePowerReport(muid: UUID): MessageBus => ActorRef => Unit = { + unsubscribe(powerReportMuid(muid)) + } + + private def powerReportMuid(muid: UUID): String = { + s"power:$muid" + } +} diff --git a/src/main/scala/org/powerapi/module/SensorChannel.scala b/src/main/scala/org/powerapi/module/SensorChannel.scala new file mode 100644 index 0000000..97dee85 --- /dev/null +++ b/src/main/scala/org/powerapi/module/SensorChannel.scala @@ -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 + +import java.util.UUID + +import org.powerapi.core.{Channel, Message} + +/** + * Main sensor message. + * + * @author Maxime Colmant + */ +trait SensorReport extends Message { + def topic: String + def muid: UUID +} + +/** + * Base channel for the Sensor components. + * + * @author Maxime Colmant + */ +trait SensorChannel extends Channel { + + type M = SensorReport +} diff --git a/src/main/scala/org/powerapi/module/SensorComponent.scala b/src/main/scala/org/powerapi/module/SensorComponent.scala new file mode 100644 index 0000000..05eb0a6 --- /dev/null +++ b/src/main/scala/org/powerapi/module/SensorComponent.scala @@ -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 + + +import akka.event.LoggingReceive +import org.powerapi.core.{APIComponent, MessageBus} + +/** + * Base trait for each PowerAPI sensor. + * Each of them should react to a MonitorTick, sense informations and then publish a SensorReport. + * + * @author Maxime Colmant + */ +abstract class SensorComponent(eventBus: MessageBus) extends APIComponent { + import org.powerapi.core.MonitorChannel.{MonitorTick, subscribeMonitorTick} + + override def preStart(): Unit = { + subscribeMonitorTick(eventBus)(self) + } + + def receive: PartialFunction[Any, Unit] = LoggingReceive { + case msg: MonitorTick => sense(msg) + } orElse default + + 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 new file mode 100644 index 0000000..8de0dd1 --- /dev/null +++ b/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala @@ -0,0 +1,98 @@ +/** + * 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.procfs + +import java.util.UUID + +import akka.actor.ActorRef +import org.powerapi.core.ClockChannel.ClockTick +import org.powerapi.core.{MessageBus, Target, TimeInStates} +import org.powerapi.module.{SensorReport, SensorChannel} + +/** + * ProcMetricsChannel channel and messages. + * + * @author Maxime Colmant + */ +object ProcMetricsChannel extends SensorChannel { + + /** + * Wrapper classes. + */ + case class TargetUsageRatio(percent: Double = 0) + case class CacheKey(muid: UUID, target: Target) + + /** + * UsageReport 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 timeInStates: time spent by the CPU in its frequencies. + * @param tick: tick origin. + */ + case class UsageReport(topic: String, + muid: UUID, + target: Target, + targetRatio: TargetUsageRatio, + timeInStates: TimeInStates = TimeInStates(Map()), + tick: ClockTick) extends SensorReport + + /** + * Topic for communicating with the Formula actors. + */ + private val topicSimpleUsageReport = "sensor:cpu-procfs-simple" + private val topicDvfsUsageReport = "sensor:cpu-procfs-dvfs" + + /** + * Publish a UsageReport in the event bus. + */ + def publishUsageReport(muid: UUID, target: Target, targetRatio: TargetUsageRatio, tick: ClockTick): MessageBus => Unit = { + publish(UsageReport(topic = topicSimpleUsageReport, + muid = muid, + target = target, + targetRatio = targetRatio, + tick = tick)) + } + + def publishUsageReport(muid: UUID, target: Target, targetRatio: TargetUsageRatio, timeInStates: TimeInStates, tick: ClockTick): MessageBus => Unit = { + publish(UsageReport(topic = topicDvfsUsageReport, + muid = muid, + target = target, + targetRatio = targetRatio, + timeInStates = timeInStates, + tick = tick)) + } + + /** + * External method used by the Formula for interacting with the bus. + */ + def subscribeSimpleUsageReport: MessageBus => ActorRef => Unit = { + subscribe(topicSimpleUsageReport) + } + + def subscribeDvfsUsageReport: MessageBus => ActorRef => Unit = { + subscribe(topicDvfsUsageReport) + } +} diff --git a/src/main/scala/org/powerapi/module/procfs/dvfs/CpuFormula.scala b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuFormula.scala new file mode 100644 index 0000000..10c3395 --- /dev/null +++ b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuFormula.scala @@ -0,0 +1,104 @@ +/** + * 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.procfs.dvfs + +import com.typesafe.config.Config +import org.powerapi.core.MessageBus +import org.powerapi.module.{PowerChannel, FormulaComponent} +import org.powerapi.module.procfs.ProcMetricsChannel + +import scala.collection.JavaConversions + +/** + * CPU formula configuration. + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +trait FormulaConfiguration extends org.powerapi.module.procfs.simple.FormulaConfiguration { + import org.powerapi.core.ConfigValue + + /** + * Map of frequencies and their associated voltages. + */ + lazy val frequencies = load { conf => + (for (item <- JavaConversions.asScalaBuffer(conf.getConfigList("powerapi.procfs.frequencies"))) + yield (item.asInstanceOf[Config].getInt("value"), item.asInstanceOf[Config].getDouble("voltage"))).toMap + } match { + case ConfigValue(freqs) => freqs + case _ => Map[Int, Double]() + } +} + +/** + * CPU formula component giving CPU energy of a given process in computing the ratio between + * global CPU energy and process CPU usage during a given period. + * + * Global CPU energy is given thanks to the well-known global formula: P = c * f * V² [1]. + * This formula operates for an unique frequency/variable but many frequencies can be used by CPU during a time period (e.g using DVFS [2]). + * Thus, this implementation weights each frequency by the time spent by CPU in working under it. + * + * @see [1] "Frequency–Voltage Cooperative CPU Power Control: A Design Rule and Its Application by Feedback Prediction" by Toyama & al. + * @see [2] http://en.wikipedia.org/wiki/Voltage_and_frequency_scaling. + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +class CpuFormula(eventBus: MessageBus) extends FormulaComponent(eventBus) with FormulaConfiguration { + import ProcMetricsChannel.{UsageReport, subscribeDvfsUsageReport} + import PowerChannel.publishPowerReport + import org.powerapi.module.PowerUnit + + override type SR = UsageReport + + def subscribeSensorReport(): Unit = { + subscribeDvfsUsageReport(eventBus)(self) + } + lazy val constant = (tdp * tdpFactor) / (frequencies.max._1 * math.pow(frequencies.max._2, 2)) + lazy val powers = frequencies.map(frequency => (frequency._1, (constant * frequency._1 * math.pow(frequency._2, 2)))) + + def power(sensorReport: UsageReport): Option[Double] = { + val totalPower = powers.foldLeft(0: Double) { + (acc, power) => acc + (power._2 * sensorReport.timeInStates.times.getOrElse(power._1, 0: Long)) + } + val time = sensorReport.timeInStates.times.foldLeft(0: Long) { + (acc, time) => acc + time._2 + } + + if (time == 0) { + None + } + else { + Some(totalPower / time) + } + } + + def compute(sensorReport: UsageReport): Unit = { + lazy val p = power(sensorReport) match { + case Some(p: Double) => p + case _ => 0d + } + + publishPowerReport(sensorReport.muid, sensorReport.target, p, PowerUnit.W, "cpu", sensorReport.tick)(eventBus) + } +} diff --git a/src/main/scala/org/powerapi/module/procfs/dvfs/CpuSensor.scala b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuSensor.scala new file mode 100644 index 0000000..192224e --- /dev/null +++ b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuSensor.scala @@ -0,0 +1,68 @@ +/** + * 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.procfs.dvfs + +import org.powerapi.core.{MessageBus, OSHelper} +import org.powerapi.module.procfs.ProcMetricsChannel + +/** + * CPU sensor component that collects data from a /proc and /sys directories + * which are typically presents under a Linux platform. + * + * @see http://www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends org.powerapi.module.procfs.simple.CpuSensor(eventBus, osHelper) { + import org.powerapi.core.MonitorChannel.MonitorTick + import ProcMetricsChannel.publishUsageReport + + /** + * Delegate class to deal with time spent within each CPU frequencies. + */ + class Frequencies { + import org.powerapi.core.TimeInStates + import ProcMetricsChannel.CacheKey + + lazy val cache = collection.mutable.Map[CacheKey, TimeInStates]() + + def refreshCache(key: CacheKey, now: TimeInStates): Unit = { + cache += (key -> now) + } + + def handleMonitorTick(tick: MonitorTick): TimeInStates = { + val now = osHelper.getTimeInStates() + val key = CacheKey(tick.muid, tick.target) + val old = cache.getOrElse(key, now) + refreshCache(key, now) + now - old + } + } + + lazy val frequencies = new Frequencies + + override def sense(monitorTick: MonitorTick): Unit = { + publishUsageReport(monitorTick.muid, monitorTick.target, targetRatio.handleMonitorTick(monitorTick), frequencies.handleMonitorTick(monitorTick), monitorTick.tick)(eventBus) + } +} diff --git a/src/main/scala/org/powerapi/module/procfs/simple/CpuFormula.scala b/src/main/scala/org/powerapi/module/procfs/simple/CpuFormula.scala new file mode 100644 index 0000000..770a251 --- /dev/null +++ b/src/main/scala/org/powerapi/module/procfs/simple/CpuFormula.scala @@ -0,0 +1,83 @@ +/** + * 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.procfs.simple + +import org.powerapi.core.MessageBus +import org.powerapi.module.{PowerChannel, FormulaComponent} +import org.powerapi.module.procfs.ProcMetricsChannel + +/** + * CPU formula configuration. + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +trait FormulaConfiguration extends org.powerapi.core.Configuration { + import org.powerapi.core.ConfigValue + + /** + * CPU Thermal Design Power (TDP) value. + * + * @see http://en.wikipedia.org/wiki/Thermal_design_power + */ + lazy val tdp = load { _.getInt("powerapi.procfs.tdp") } match { + case ConfigValue(value) => value + case _ => 0 + } + + /** + * CPU Thermal Design Power (TDP) factor. + * + * @see [1], JouleSort: A Balanced Energy-Efficiency Benchmark, by Rivoire et al. + */ + lazy val tdpFactor = load { _.getDouble("powerapi.procfs.tdp-factor") } match { + case ConfigValue(value) => value + case _ => 0.7 + } +} + +/** + * Implements a CpuFormula by making the ratio between maximum CPU power (obtained by multiplying + * its Thermal Design Power (TDP) value by a specific factor) and the process CPU usage. + * + * @see http://en.wikipedia.org/wiki/Thermal_design_power + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +class CpuFormula(eventBus: MessageBus) extends FormulaComponent(eventBus) with FormulaConfiguration { + import ProcMetricsChannel.{UsageReport, subscribeSimpleUsageReport} + import PowerChannel.publishPowerReport + import org.powerapi.module.PowerUnit + + override type SR = UsageReport + + def subscribeSensorReport(): Unit = { + subscribeSimpleUsageReport(eventBus)(self) + } + + def compute(sensorReport: UsageReport): Unit = { + lazy val power = (tdp * tdpFactor) * sensorReport.targetRatio.percent + publishPowerReport(sensorReport.muid, sensorReport.target, power, PowerUnit.W, "cpu", sensorReport.tick)(eventBus) + } +} diff --git a/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala b/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala new file mode 100644 index 0000000..f949950 --- /dev/null +++ b/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala @@ -0,0 +1,114 @@ +/** + * 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.procfs.simple + +import org.powerapi.core.{MessageBus, OSHelper} +import org.powerapi.module.SensorComponent +import org.powerapi.module.procfs.ProcMetricsChannel + +/** + * CPU sensor component that collects data from a /proc and /sys directories + * which are typically presents under a Linux platform. + * + * @see http://www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html + * + * @author Aurélien Bourdon + * @author Maxime Colmant + */ +class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponent(eventBus) { + import org.powerapi.core.MonitorChannel.MonitorTick + import ProcMetricsChannel.publishUsageReport + + /** + * Delegate class collecting time information contained into both globalStatPath and processStatPath files + * and providing the target CPU percent usage. + */ + class TargetRatio { + import org.powerapi.core.{All, Application, Process} + import ProcMetricsChannel.CacheKey + + /** + * Internal cache, used to get the diff between two ClockTick. + */ + lazy val cache = collection.mutable.Map[CacheKey, (Long, Long)]() + + def refreshCache(key: CacheKey, now: (Long, Long)): Unit = { + cache += (key -> now) + } + + def handleProcessTarget(process: Process): (Long, Long) = { + lazy val processTime: Long = osHelper.getProcessCpuTime(process) match { + case Some(value) => value + case _ => 0l + } + lazy val globalTime: Long = osHelper.getGlobalCpuTime() match { + case Some(value) => value + case _ => 0l + } + + (processTime, globalTime) + } + + def handleApplicationTarget(application: Application): (Long, Long) = { + lazy val processTime: Long = osHelper.getProcesses(application).foldLeft(0: Long) { + (acc, process: Process) => { + osHelper.getProcessCpuTime(process) match { + case Some(value) => acc + value + case _ => acc + } + } + } + lazy val globalTime: Long = osHelper.getGlobalCpuTime() match { + case Some(value) => value + case _ => 0l + } + (processTime, globalTime) + } + + def handleMonitorTick(tick: MonitorTick): ProcMetricsChannel.TargetUsageRatio = { + val now = tick.target match { + case process: Process => handleProcessTarget(process) + case application: Application => handleApplicationTarget(application) + case All => (0l, 0l) + } + + val key = CacheKey(tick.muid, tick.target) + val old = cache.getOrElse(key, now) + refreshCache(key, now) + + val globalDiff = now._2 - old._2 + if (globalDiff <= 0) { + ProcMetricsChannel.TargetUsageRatio(0) + } + else { + ProcMetricsChannel.TargetUsageRatio((now._1 - old._1).doubleValue / globalDiff) + } + } + } + + lazy val targetRatio = new TargetRatio + + def sense(monitorTick: MonitorTick): Unit = { + publishUsageReport(monitorTick.muid, monitorTick.target, targetRatio.handleMonitorTick(monitorTick), monitorTick.tick)(eventBus) + } +} diff --git a/src/test/resources/application.conf b/src/test/resources/application.conf index 8205e70..b9086ea 100644 --- a/src/test/resources/application.conf +++ b/src/test/resources/application.conf @@ -1,2 +1,3 @@ include "akka" include "configuration-suite" +include "dvfs-cpuformula-suite" diff --git a/src/test/resources/dvfs-cpuformula-suite.conf b/src/test/resources/dvfs-cpuformula-suite.conf new file mode 100644 index 0000000..63f0493 --- /dev/null +++ b/src/test/resources/dvfs-cpuformula-suite.conf @@ -0,0 +1,5 @@ +powerapi.procfs.frequencies = [ + { value = 1800002, voltage = 1.31 } + { value = 2100002, voltage = 1.41 } + { value = 2400003, voltage = 1.5 } +] diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..32735c3 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/resources/proc/1/stat b/src/test/resources/proc/1/stat new file mode 100644 index 0000000..3e9e692 --- /dev/null +++ b/src/test/resources/proc/1/stat @@ -0,0 +1 @@ +1 (app1) R 16 16 1 34816 2426 1077960704 2686 135 0 0 33 2 0 0 20 0 19 0 1227610 1824075776 3818 18446744073709551615 4194304 4196724 140736700782272 140736700764944 139861678892651 0 0 0 16800975 18446744073709551615 0 0 17 6 0 0 0 0 0 6294960 6295616 20422656 140736700786902 140736700787195 140736700787195 140736700788714 0 diff --git a/src/test/resources/proc/1/task/1000/to-include b/src/test/resources/proc/1/task/1000/to-include new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/proc/1/task/1001/to-include b/src/test/resources/proc/1/task/1001/to-include new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/proc/2/stat b/src/test/resources/proc/2/stat new file mode 100644 index 0000000..c98483d --- /dev/null +++ b/src/test/resources/proc/2/stat @@ -0,0 +1 @@ +2 (app2-1) R 1 16 1 34816 2429 1077960704 251 311 0 0 10 5 0 0 20 0 1 0 1227609 18423808 694 18446744073709551615 4194304 5173212 140734976343792 140734976342504 140581950608844 0 65536 4 65538 0 0 0 17 5 0 0 0 0 0 7273968 7310504 30318592 140734976346584 140734976346617 140734976346617 140734976348137 0 diff --git a/src/test/resources/proc/3/stat b/src/test/resources/proc/3/stat new file mode 100644 index 0000000..455b9ff --- /dev/null +++ b/src/test/resources/proc/3/stat @@ -0,0 +1 @@ +3 (app2-2) T 16 16 1 34816 2430 1077960704 2686 135 0 0 3 5 0 0 20 0 19 0 1227610 1824075776 3818 18446744073709551615 4194304 4196724 140736700782272 140736700764944 139861678892651 0 0 0 16800975 18446744073709551615 0 0 17 6 0 0 0 0 0 6294960 6295616 20422656 140736700786902 140736700787195 140736700787195 140736700788714 0 diff --git a/src/test/resources/proc/stat b/src/test/resources/proc/stat new file mode 100644 index 0000000..1a6961f --- /dev/null +++ b/src/test/resources/proc/stat @@ -0,0 +1,16 @@ +cpu 43171 1 24917 25883594 1160 19 1477 0 2 1 +cpu0 13663 0 3926 3221473 293 19 1404 0 2 1 +cpu1 3357 0 2369 3241072 213 0 6 0 0 0 +cpu2 2832 0 2332 3235995 244 0 7 0 0 0 +cpu3 5280 0 2169 3237871 135 0 8 0 0 0 +cpu4 6079 0 7078 3231394 126 0 6 0 0 0 +cpu5 3775 0 2166 3241116 69 0 7 0 0 0 +cpu6 4472 0 2641 3235639 34 0 2 0 0 0 +cpu7 3713 1 2236 3239034 46 0 37 0 0 0 +intr 2216025 27 10 0 0 17 0 0 0 1 0 0 0 149 0 0 0 18586 0 0 40153 492289 89742 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +ctxt 3707648 +btime 1416406981 +processes 24895 +procs_running 1 +procs_blocked 0 +softirq 920697 0 321918 79 78732 85406 0 1 212897 2298 219366 diff --git a/src/test/resources/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state b/src/test/resources/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state new file mode 100644 index 0000000..9abea96 --- /dev/null +++ b/src/test/resources/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state @@ -0,0 +1,4 @@ +4000000 4 +3000000 3 +2000000 2 +1000000 1 diff --git a/src/test/resources/sys/devices/system/cpu/cpu1/cpufreq/stats/time_in_state b/src/test/resources/sys/devices/system/cpu/cpu1/cpufreq/stats/time_in_state new file mode 100644 index 0000000..9abea96 --- /dev/null +++ b/src/test/resources/sys/devices/system/cpu/cpu1/cpufreq/stats/time_in_state @@ -0,0 +1,4 @@ +4000000 4 +3000000 3 +2000000 2 +1000000 1 diff --git a/src/test/resources/sys/devices/system/cpu/cpu2/cpufreq/stats/time_in_state b/src/test/resources/sys/devices/system/cpu/cpu2/cpufreq/stats/time_in_state new file mode 100644 index 0000000..9abea96 --- /dev/null +++ b/src/test/resources/sys/devices/system/cpu/cpu2/cpufreq/stats/time_in_state @@ -0,0 +1,4 @@ +4000000 4 +3000000 3 +2000000 2 +1000000 1 diff --git a/src/test/resources/sys/devices/system/cpu/cpu3/cpufreq/stats/time_in_state b/src/test/resources/sys/devices/system/cpu/cpu3/cpufreq/stats/time_in_state new file mode 100644 index 0000000..9abea96 --- /dev/null +++ b/src/test/resources/sys/devices/system/cpu/cpu3/cpufreq/stats/time_in_state @@ -0,0 +1,4 @@ +4000000 4 +3000000 3 +2000000 2 +1000000 1 diff --git a/src/test/scala/org/powerapi/core/UnitTest.scala b/src/test/scala/org/powerapi/UnitTest.scala similarity index 97% rename from src/test/scala/org/powerapi/core/UnitTest.scala rename to src/test/scala/org/powerapi/UnitTest.scala index 3c5be13..ec7bfd4 100644 --- a/src/test/scala/org/powerapi/core/UnitTest.scala +++ b/src/test/scala/org/powerapi/UnitTest.scala @@ -2,9 +2,9 @@ * 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 @@ -21,7 +21,7 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ -package org.powerapi.test +package org.powerapi import akka.actor.ActorSystem import akka.testkit.{ImplicitSender, TestKit} diff --git a/src/test/scala/org/powerapi/core/ClockSuite.scala b/src/test/scala/org/powerapi/core/ClockSuite.scala index a579535..ae02057 100644 --- a/src/test/scala/org/powerapi/core/ClockSuite.scala +++ b/src/test/scala/org/powerapi/core/ClockSuite.scala @@ -20,7 +20,6 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core import akka.actor.{Actor, ActorNotFound, ActorRef, ActorSystem, Props} @@ -28,16 +27,15 @@ import akka.pattern.gracefulStop import akka.testkit.{EventFilter, TestKit, TestProbe} import akka.util.Timeout import com.typesafe.config.ConfigFactory -import org.powerapi.test.UnitTest - +import org.powerapi.UnitTest import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration} class ClockMockSubscriber(eventBus: MessageBus, frequency: FiniteDuration) extends Actor { - import org.powerapi.core.ClockChannel.{ClockTick, subscribeClock} + import org.powerapi.core.ClockChannel.{ClockTick, subscribeClockTick} override def preStart() = { - subscribeClock(frequency)(eventBus)(self) + subscribeClockTick(frequency)(eventBus)(self) } def receive = active(0) @@ -50,7 +48,7 @@ class ClockMockSubscriber(eventBus: MessageBus, frequency: FiniteDuration) exten } class ClockSuite(system: ActorSystem) extends UnitTest(system) { - import org.powerapi.core.ClockChannel.{ClockStart, ClockStop, formatClockChildName, startClock, stopClock, unsubscribeClock} + import org.powerapi.core.ClockChannel.{ClockStart, ClockStop, formatClockChildName, startClock, stopClock, unsubscribeClockTick} implicit val timeout = Timeout(1.seconds) def this() = this(ActorSystem("ClockSuite")) @@ -108,6 +106,8 @@ class ClockSuite(system: ActorSystem) extends UnitTest(system) { } "A ClockChild actor" should "produce Ticks, stop its own timer if needed and thus stop to publish Ticks" in new Bus { + import java.lang.Thread + val _system = ActorSystem("ClockSuiteTest2", eventListener) val frequency = 25.milliseconds @@ -141,6 +141,8 @@ class ClockSuite(system: ActorSystem) extends UnitTest(system) { } it should "handle only one timer and stop it if there is no subscription" in new Bus { + import java.lang.Thread + val _system = ActorSystem("ClockSuiteTest3", eventListener) val frequency = 25.milliseconds @@ -181,6 +183,8 @@ class ClockSuite(system: ActorSystem) extends UnitTest(system) { } "A Clocks actor" should "handle ClockChild actors and the subscribers have to receive tick messages for their frequencies" in new Bus { + import java.lang.Thread + val _system = ActorSystem("ClockSuiteTest4") val frequency1 = 50.milliseconds @@ -250,7 +254,7 @@ class ClockSuite(system: ActorSystem) extends UnitTest(system) { } val testSubscriber = subscribersF1.head - unsubscribeClock(frequency1)(eventBus)(testSubscriber) + unsubscribeClockTick(frequency1)(eventBus)(testSubscriber) startClock(frequency1)(eventBus) Thread.sleep(250) stopClock(frequency1)(eventBus) @@ -290,6 +294,8 @@ class ClockSuite(system: ActorSystem) extends UnitTest(system) { } it can "handle a large number of clocks and the subscribers have to receive tick messages for their frequencies" in new Bus { + import java.lang.Thread + val _system = ActorSystem("ClockSuiteTest5") val clocks = _system.actorOf(Props(classOf[Clocks], eventBus), "clocks5") diff --git a/src/test/scala/org/powerapi/core/ComponentSuite.scala b/src/test/scala/org/powerapi/core/ComponentSuite.scala index 48b5caa..43c556c 100644 --- a/src/test/scala/org/powerapi/core/ComponentSuite.scala +++ b/src/test/scala/org/powerapi/core/ComponentSuite.scala @@ -20,7 +20,6 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core import akka.actor.SupervisorStrategy.{Directive, Escalate, Restart, Resume, Stop} @@ -28,15 +27,15 @@ import akka.actor.{ActorRef, ActorSystem, Props, Terminated} import akka.event.LoggingReceive import akka.testkit.{EventFilter, TestActorRef, TestKit} import com.typesafe.config.ConfigFactory -import org.powerapi.test.UnitTest +import org.powerapi.UnitTest -class TestComponent extends Component { +class TestActorComponent extends ActorComponent { def receive = LoggingReceive { case "msg" => sender ! "ok" } orElse default } -class TestSupervisor(f: PartialFunction[Throwable, Directive]) extends Component with Supervisor { +class TestSupervisor(f: PartialFunction[Throwable, Directive]) extends ActorComponent with Supervisor { def handleFailure: PartialFunction[Throwable, Directive] = f def receive = { @@ -45,7 +44,7 @@ class TestSupervisor(f: PartialFunction[Throwable, Directive]) extends Component } } -class TestChild extends Component { +class TestChild extends ActorComponent { var state = 0 def receive = { @@ -63,8 +62,12 @@ class ComponentSuite(system: ActorSystem) extends UnitTest(system) { TestKit.shutdownActorSystem(system) } + trait Bus { + val eventBus = new MessageBus + } + "A component" should "have a default behavior and a processing one" in { - val component = TestActorRef(Props(classOf[TestComponent]))(system) + val component = TestActorRef(Props(classOf[TestActorComponent]))(system) component ! "msg" expectMsg("ok") intercept[UnsupportedOperationException] { component.receive(new Exception("oups")) } @@ -101,7 +104,7 @@ class ComponentSuite(system: ActorSystem) extends UnitTest(system) { EventFilter[IllegalArgumentException](occurrences = 1, source = child.path.toString).intercept({ watch(child) child ! new IllegalArgumentException("bad argument") - expectMsgPF() { case Terminated(child) => () } + expectMsgPF() { case Terminated(_) => () } })(system) EventFilter[Exception]("crash", occurrences = 1, source = supervisor.path.toString).intercept({ @@ -112,7 +115,7 @@ class ComponentSuite(system: ActorSystem) extends UnitTest(system) { child ! "state" expectMsg(42) child ! new Exception("crash") - expectMsgPF() { case t @ Terminated(child) if t.existenceConfirmed => () } + expectMsgPF() { case t @ Terminated(_) if t.existenceConfirmed => () } })(system) } diff --git a/src/test/scala/org/powerapi/core/ConfigurationSuite.scala b/src/test/scala/org/powerapi/core/ConfigurationSuite.scala index a76646e..fe3f50e 100644 --- a/src/test/scala/org/powerapi/core/ConfigurationSuite.scala +++ b/src/test/scala/org/powerapi/core/ConfigurationSuite.scala @@ -20,14 +20,12 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core import akka.actor.ActorSystem import akka.testkit.TestKit import com.typesafe.config.{Config, ConfigException} -import org.powerapi.test.UnitTest - +import org.powerapi.UnitTest import scala.collection.JavaConversions class ConfigurationMock extends Configuration { diff --git a/src/test/scala/org/powerapi/core/MessageBusSuite.scala b/src/test/scala/org/powerapi/core/MessageBusSuite.scala index 0bc8139..8fa1a0d 100644 --- a/src/test/scala/org/powerapi/core/MessageBusSuite.scala +++ b/src/test/scala/org/powerapi/core/MessageBusSuite.scala @@ -20,16 +20,11 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ - package org.powerapi.core -import java.util.UUID - import akka.actor.ActorSystem import akka.testkit.TestKit -import org.powerapi.test.UnitTest - -case class MessageReport(muid: UUID, topic: String) extends Report +import org.powerapi.UnitTest class MessageBusSuite(system: ActorSystem) extends UnitTest(system) { @@ -41,16 +36,15 @@ class MessageBusSuite(system: ActorSystem) extends UnitTest(system) { "The MessageBus" should "handle messages by topic" in { val eventBus = new MessageBus - val muid = UUID.randomUUID() - val report = MessageReport(muid, "topic1") - val report2 = MessageReport(muid, "topic2") + val msg1 = new Message { val topic = "topic1" } + val msg2 = new Message { val topic = "topic2" } eventBus.subscribe(testActor, "topic1") - eventBus.publish(report) - eventBus.publish(report2) - expectMsg(report) + eventBus.publish(msg1) + eventBus.publish(msg2) + expectMsg(msg1) eventBus.unsubscribe(testActor) - eventBus.publish(report) + eventBus.publish(msg2) expectNoMsg() } } diff --git a/src/test/scala/org/powerapi/core/MonitorSuite.scala b/src/test/scala/org/powerapi/core/MonitorSuite.scala index a105d57..421dbed 100644 --- a/src/test/scala/org/powerapi/core/MonitorSuite.scala +++ b/src/test/scala/org/powerapi/core/MonitorSuite.scala @@ -1,28 +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.core import java.util.UUID - import akka.actor.{Actor, ActorNotFound, ActorRef, ActorSystem, Props} import akka.pattern.gracefulStop import akka.testkit.{EventFilter, TestKit, TestProbe} import akka.util.Timeout import com.typesafe.config.ConfigFactory -import org.powerapi.test.UnitTest - +import org.powerapi.UnitTest import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} class MonitorMockSubscriber(eventBus: MessageBus) extends Actor { - import org.powerapi.core.MonitorChannel.{MonitorTarget, subscribeTarget} + import org.powerapi.core.MonitorChannel.{MonitorTick, subscribeMonitorTick} override def preStart() = { - subscribeTarget(eventBus)(self) + subscribeMonitorTick(eventBus)(self) } def receive = active(0) def active(acc: Int): Actor.Receive = { - case _: MonitorTarget => context become active(acc + 1) + case _: MonitorTick => context become active(acc + 1) case "reset" => context become active(0) case "get" => sender ! acc } @@ -87,6 +107,8 @@ class MonitorSuite(system: ActorSystem) extends UnitTest(system) { } "A MonitorChild actor" should "start to listen ticks for its frequency and produce messages" in new Bus { + import java.lang.Thread + val _system = ActorSystem("MonitorSuiteTest2", eventListener) val clocks = _system.actorOf(Props(classOf[Clocks], eventBus), "clocks2") @@ -121,7 +143,7 @@ class MonitorSuite(system: ActorSystem) extends UnitTest(system) { subscriber ! "get" // We assume a service quality of 90% (regarding the number of processed messages). - expectMsgClass(classOf[Int]) should be >= (targets.size * 10 * 0.9).toInt + expectMsgClass(classOf[Int]) should be >= (targets.size * (10 * 0.9)).toInt Await.result(gracefulStop(clocks, timeout.duration), timeout.duration) Await.result(gracefulStop(monitor, timeout.duration), timeout.duration) @@ -132,6 +154,8 @@ class MonitorSuite(system: ActorSystem) extends UnitTest(system) { } it can "handle a large number of targets" in new Bus { + import java.lang.Thread + val _system = ActorSystem("MonitorSuiteTest3") val frequency = 25.milliseconds @@ -164,7 +188,7 @@ class MonitorSuite(system: ActorSystem) extends UnitTest(system) { subscriber ! "get" // We assume a service quality of 90% (regarding the number of processed messages). - expectMsgClass(classOf[Int]) should be >= (targets.size * 10 * 0.9).toInt + expectMsgClass(classOf[Int]) should be >= (targets.size * (10 * 0.9)).toInt Await.result(gracefulStop(clocks, timeout.duration), timeout.duration) Await.result(gracefulStop(monitor, timeout.duration), timeout.duration) @@ -175,7 +199,10 @@ class MonitorSuite(system: ActorSystem) extends UnitTest(system) { } "A Monitors actor" should "handle its MonitorChild actors and subscribers have to receive messages" in new Bus { + import java.lang.Thread + val _system = ActorSystem("MonitorSuiteTest4") + val clocks = _system.actorOf(Props(classOf[Clocks], eventBus), "clocks4") val monitors = _system.actorOf(Props(classOf[Monitors], eventBus), "monitors4") @@ -208,7 +235,7 @@ class MonitorSuite(system: ActorSystem) extends UnitTest(system) { for(i <- 0 until 100) { subscribers(i) ! "get" // We assume a service quality of 90% (regarding the number of processed messages). - expectMsgClass(classOf[Int]) should be >= (targets.size * 10 * 0.9).toInt + expectMsgClass(classOf[Int]) should be >= (targets.size * (10 * 0.9)).toInt Await.result(gracefulStop(subscribers(i), timeout.duration), timeout.duration) } @@ -219,6 +246,8 @@ class MonitorSuite(system: ActorSystem) extends UnitTest(system) { } it should "handle a large number of monitors" in new Bus { + import java.lang.Thread + val _system = ActorSystem("MonitorSuiteTest5") val clocks = _system.actorOf(Props(classOf[Clocks], eventBus), "clocks5") val monitors = _system.actorOf(Props(classOf[Monitors], eventBus), "monitors5") diff --git a/src/test/scala/org/powerapi/core/OSHelperSuite.scala b/src/test/scala/org/powerapi/core/OSHelperSuite.scala new file mode 100644 index 0000000..2ab76e0 --- /dev/null +++ b/src/test/scala/org/powerapi/core/OSHelperSuite.scala @@ -0,0 +1,113 @@ +/** + * 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.core + +import akka.actor.ActorSystem +import akka.testkit.TestKit +import akka.util.Timeout +import org.powerapi.UnitTest +import scala.concurrent.duration.DurationInt + +class OSHelperSuite(system: ActorSystem) extends UnitTest(system) { + + implicit val timeout = Timeout(1.seconds) + + def this() = this(ActorSystem("SimpleCpuSensorSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + val basepath = getClass.getResource("/").getPath + + "The method getThreads in the LinuxHelper" should "return the threads created by a given process" in { + val helper = new LinuxHelper { + override lazy val taskPath = s"${basepath}proc/%?pid/task" + } + + helper.getThreads(Process(1)) should equal(List(Thread(1000), Thread(1001))) + } + + "The method getProcessCpuTime in the LinuxHelper" should "return the process cpu time of a given process" in { + val helper = new LinuxHelper { + override lazy val processStatPath = s"${basepath}proc/%?pid/stat" + } + + helper.getProcessCpuTime(Process(1)) match { + case Some(35) => assert(true) + case _ => assert(false) + } + + helper.getProcessCpuTime(Process(10)) match { + case None => assert(true) + case _ => assert(false) + } + } + + "The method getGlobalCpuTime in the LinuxHelper" should "return the global cpu time" in { + val helper = new LinuxHelper { + override lazy val globalStatPath = s"${basepath}/proc/stat" + } + + val badHelper = new LinuxHelper { + override lazy val globalStatPath = s"${basepath}/proc/stats" + } + + val globalTime = 43171 + 1 + 24917 + 25883594 + 1160 + 19 + 1477 + 0 + + helper.getGlobalCpuTime() match { + case Some(globalTime) => assert(true) + case _ => assert(false) + } + + badHelper.getGlobalCpuTime() match { + case None => assert(true) + case _ => assert(false) + } + } + + "The method getTimeInStates in the LinuxHelper" should "return the time spent by the CPU in each frequency if the dvfs is enabled" in { + val helper = new LinuxHelper { + override lazy val timeInStatePath = s"${basepath}sys/devices/system/cpu/cpu%?index/cpufreq/stats/time_in_state" + override lazy val cores = 4 + } + + val badHelper = new LinuxHelper { + override lazy val timeInStatePath = s"${basepath}/sys/devices/system/cpu/cpu%?index/cpufreq/stats/time_in_states" + override lazy val cores = 4 + } + + helper.getTimeInStates() should equal( + TimeInStates(Map(4000000l -> 16l, 3000000l -> 12l, 2000000l -> 8l, 1000000l -> 4l)) + ) + + badHelper.getTimeInStates() should equal(TimeInStates(Map())) + } + + "A TimeInStates case class" should "compute the difference with another one" in { + val timesLeft = TimeInStates(Map(1l -> 10l, 2l -> 20l, 3l -> 30l, 4l -> 15l)) + val timesRight = TimeInStates(Map(1l -> 1l, 2l -> 2l, 3l -> 3l, 100l -> 100l)) + + (timesLeft - timesRight) should equal(TimeInStates(Map(1l -> 9l, 2l -> 18l, 3l -> 27l, 4l -> 15l))) + } +} diff --git a/src/test/scala/org/powerapi/module/FormulaSuite.scala b/src/test/scala/org/powerapi/module/FormulaSuite.scala new file mode 100644 index 0000000..bea4c39 --- /dev/null +++ b/src/test/scala/org/powerapi/module/FormulaSuite.scala @@ -0,0 +1,84 @@ +/** + * 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 + +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} + +object SensorMockChannel extends SensorChannel { + private val topic = "test" + + case class SensorMockReport(topic: String, muid: UUID, power: Double) extends SensorReport + + def subscribeMockMessage: MessageBus => ActorRef => Unit = { + subscribe(topic) + } + + def publishSensorMockReport(muid: UUID, power: Double): MessageBus => Unit = { + publish(SensorMockReport(topic, muid, power)) + } +} + +class FormulaMock(eventBus: MessageBus, actorRef: ActorRef, coeff: Double) extends FormulaComponent(eventBus) { + import org.powerapi.module.SensorMockChannel.{SensorMockReport, subscribeMockMessage} + + type SR = SensorMockReport + + def subscribeSensorReport(): Unit = { + subscribeMockMessage(eventBus)(self) + } + + def compute(sensorReport: SensorMockReport): Unit = { + actorRef ! sensorReport.power * coeff + } +} + +class FormulaSuite(system: ActorSystem) extends UnitTest(system) { + + def this() = this(ActorSystem("FormulaSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + trait Bus { + val eventBus = new MessageBus + } + + "A Formula" should "process SensorReport messages" in new Bus { + import org.powerapi.module.SensorMockChannel.publishSensorMockReport + + val coeff = 10d + val formulaMock = TestActorRef(Props(classOf[FormulaMock], eventBus, testActor, coeff))(system) + + val muid = UUID.randomUUID() + val power = 2.2d + + publishSensorMockReport(muid, power)(eventBus) + expectMsgClass(classOf[Double]) should equal(power * coeff) + } +} diff --git a/src/test/scala/org/powerapi/module/SensorSuite.scala b/src/test/scala/org/powerapi/module/SensorSuite.scala new file mode 100644 index 0000000..162a201 --- /dev/null +++ b/src/test/scala/org/powerapi/module/SensorSuite.scala @@ -0,0 +1,70 @@ +/** + * 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 + +import java.util.UUID + +import akka.actor.{ActorRef, ActorSystem, Props} +import akka.testkit.{TestActorRef, TestKit} +import org.powerapi.UnitTest +import org.powerapi.core.{MessageBus, Process} + +import scala.concurrent.duration.DurationInt + +class SensorMock(eventBus: MessageBus, actorRef: ActorRef) extends SensorComponent(eventBus) { + import org.powerapi.core.MonitorChannel.MonitorTick + + def sense(monitorTick: MonitorTick): Unit = { + actorRef ! monitorTick + } +} +class SensorSuite(system: ActorSystem) extends UnitTest(system) { + + def this() = this(ActorSystem("SensorSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + trait Bus { + val eventBus = new MessageBus + } + + "A Sensor" should "process MonitorTick messages" in new Bus { + import org.powerapi.core.ClockChannel.ClockTick + import org.powerapi.core.MonitorChannel.{MonitorTick, publishMonitorTick} + + val sensorMock = TestActorRef(Props(classOf[SensorMock], eventBus, testActor))(system) + + val muid = UUID.randomUUID() + val target = Process(1) + val clockTick = ClockTick("test", 25.milliseconds) + + publishMonitorTick(muid, target, clockTick)(eventBus) + + expectMsgClass(classOf[MonitorTick]) match { + case MonitorTick(_, id, targ, tick) if muid == id && target == targ && clockTick == tick => assert(true) + case _ => assert(false) + } + } +} diff --git a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala new file mode 100644 index 0000000..ee9c51d --- /dev/null +++ b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala @@ -0,0 +1,127 @@ +/** + * 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.procfs.dvfs + +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 org.powerapi.module.PowerChannel +import org.powerapi.module.procfs.ProcMetricsChannel +import scala.concurrent.duration.DurationInt + +trait DvfsCpuFormulaConfigurationMock extends FormulaConfiguration { + override lazy val tdp = 220 + override lazy val tdpFactor = 0.7 +} + +class DvfsCpuFormulaMock(messageBus: MessageBus) + extends CpuFormula(messageBus) + with DvfsCpuFormulaConfigurationMock + +class DvfsCpuFormulaSuite(system: ActorSystem) extends UnitTest(system) { + + implicit val timeout = Timeout(1.seconds) + + def this() = this(ActorSystem("DvfsCpuFormulaSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + val eventBus = new MessageBus + val formulaMock = TestActorRef(Props(classOf[DvfsCpuFormulaMock], eventBus), "dvfs-cpuFormula")(system) + val frequencies = Map(1800002 -> 1.31, 2100002 -> 1.41, 2400003 -> 1.5) + + "A dvfs cpu formula" should "read frequencies in a configuration file" in { + formulaMock.underlyingActor.asInstanceOf[CpuFormula].frequencies should equal(frequencies) + } + + it should "compute correctly the constant" in { + formulaMock.underlyingActor.asInstanceOf[CpuFormula].constant should equal((220 * 0.7) / (2400003 * math.pow(1.5, 2))) + } + + it should "compute powers for each frequency" in { + formulaMock.underlyingActor.asInstanceOf[CpuFormula].powers should equal(Map( + 1800002 -> formulaMock.underlyingActor.asInstanceOf[CpuFormula].constant * 1800002 * math.pow(1.31, 2), + 2100002 -> formulaMock.underlyingActor.asInstanceOf[CpuFormula].constant * 2100002 * math.pow(1.41, 2), + 2400003 -> formulaMock.underlyingActor.asInstanceOf[CpuFormula].constant * 2400003 * math.pow(1.5, 2) + )) + } + + it should "compute correctly the process' power" in { + import org.powerapi.core.{Process,TimeInStates} + import org.powerapi.core.ClockChannel.ClockTick + import ProcMetricsChannel.{UsageReport, TargetUsageRatio} + + val topic = "test" + val muid = UUID.randomUUID() + val target = Process(1) + val targetRatio = TargetUsageRatio(0.5) + val timeInStates = TimeInStates(Map(1800002l -> 1l, 2100002l -> 2l, 2400003l -> 3l)) + val tick = ClockTick("clock", 25.milliseconds) + + val sensorReport = UsageReport(topic, muid, target, targetRatio, timeInStates, tick) + + formulaMock.underlyingActor.asInstanceOf[CpuFormula].power(sensorReport) should equal( + Some( + ( + formulaMock.underlyingActor.asInstanceOf[CpuFormula].powers(1800002) * 1 + + formulaMock.underlyingActor.asInstanceOf[CpuFormula].powers(2100002) * 2 + + formulaMock.underlyingActor.asInstanceOf[CpuFormula].powers(2400003) * 3 + ) / (1 + 2 + 3) + ) + ) + } + + it should "process a SensorReport and then publish a PowerReport" in { + import org.powerapi.core.{Process,TimeInStates} + import org.powerapi.core.ClockChannel.ClockTick + import PowerChannel.{PowerReport, subscribePowerReport} + import ProcMetricsChannel.{publishUsageReport, TargetUsageRatio} + import org.powerapi.module.PowerUnit + + val muid = UUID.randomUUID() + val target = Process(1) + val targetRatio = TargetUsageRatio(0.5) + val timeInStates = TimeInStates(Map(1800002l -> 1l, 2100002l -> 2l, 2400003l -> 3l)) + val tickMock = ClockTick("test", 25.milliseconds) + val power = ( + formulaMock.underlyingActor.asInstanceOf[CpuFormula].powers(1800002) * 1 + + formulaMock.underlyingActor.asInstanceOf[CpuFormula].powers(2100002) * 2 + + formulaMock.underlyingActor.asInstanceOf[CpuFormula].powers(2400003) * 3 + ) / (1 + 2 + 3) + + subscribePowerReport(muid)(eventBus)(testActor) + publishUsageReport(muid, target, targetRatio, timeInStates, tickMock)(eventBus) + + expectMsgClass(classOf[PowerReport]) match { + case PowerReport(_, id, targ, pow, PowerUnit.W, "cpu", tic) if muid == id && target == targ && power == pow && tickMock == tic => assert(true) + case _ => assert(false) + } + } +} diff --git a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuSensorSuite.scala b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuSensorSuite.scala new file mode 100644 index 0000000..e6ca067 --- /dev/null +++ b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuSensorSuite.scala @@ -0,0 +1,112 @@ +/** + * 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.procfs.dvfs + +import java.util.UUID + +import akka.actor.{ActorSystem, Props} +import akka.testkit.{TestActorRef, TestKit} +import akka.util.Timeout +import org.powerapi.UnitTest +import org.powerapi.core.{TimeInStates, MessageBus, OSHelper} + +import scala.concurrent.duration.DurationInt + +class OSHelperMock extends OSHelper { + import org.powerapi.core.{Application, Process, Thread} + + 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(1) => Some(33 + 2) + 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(4000000l -> 16l, 3000000l -> 12l, 2000000l -> 8l, 1000000l -> 4l)) +} + +class DvfsCpuSensorSuite(system: ActorSystem) extends UnitTest(system) { + import org.powerapi.core.ClockChannel.ClockTick + import org.powerapi.core.MonitorChannel.MonitorTick + import org.powerapi.core.{Application, Process} + import org.powerapi.module.procfs.ProcMetricsChannel.{CacheKey, UsageReport} + + implicit val timeout = Timeout(1.seconds) + + def this() = this(ActorSystem("DvfsCpuSensorSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + val eventBus = new MessageBus + + val cpuSensor = TestActorRef(Props(classOf[CpuSensor], eventBus, new OSHelperMock()), "dvfs-CpuSensor")(system) + + "Frequencies' cache" should "be correctly updated during process phase" in { + val muid = UUID.randomUUID() + val processTarget = Process(1) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.cache should have size 0 + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.handleMonitorTick(MonitorTick("test", muid, processTarget, ClockTick("test", 25.milliseconds))) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.cache should equal( + Map(CacheKey(muid, processTarget) -> TimeInStates(Map(4000000l -> 16l, 3000000l -> 12l, 2000000l -> 8l, 1000000l -> 4l))) + ) + } + + "A dvfs CpuSensor" should "process a MonitorTicks message and then publish a UsageReport" in { + import org.powerapi.core.MonitorChannel.publishMonitorTick + import org.powerapi.module.procfs.ProcMetricsChannel.subscribeDvfsUsageReport + + val muid = UUID.randomUUID() + val tickMock = ClockTick("test", 25.milliseconds) + val timeInStates = TimeInStates(Map(4000000l -> 6l, 3000000l -> 2l, 2000000l -> 2l, 1000000l -> 2l)) + + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.refreshCache(CacheKey(muid, Process(1)), + TimeInStates(Map(4000000l -> 10l, 3000000l -> 10l, 2000000l -> 6l, 1000000l -> 2l)) + ) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.refreshCache(CacheKey(muid, Application("app")), + TimeInStates(Map(4000000l -> 10l, 3000000l -> 10l, 2000000l -> 6l, 1000000l -> 2l)) + ) + + subscribeDvfsUsageReport(eventBus)(testActor) + + publishMonitorTick(muid, Process(1), tickMock)(eventBus) + expectMsgClass(classOf[UsageReport]) match { + case UsageReport(_, id, Process(1), _, times ,_) if muid == id && timeInStates == times => assert(true) + case _ => assert(false) + } + publishMonitorTick(muid, Application("app"), tickMock)(eventBus) + expectMsgClass(classOf[UsageReport]) match { + case UsageReport(_, id, Application("app"), _, times ,_) if muid == id && timeInStates == times => assert(true) + case _ => assert(false) + } + } +} diff --git a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuFormulaSuite.scala b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuFormulaSuite.scala new file mode 100644 index 0000000..617ce4a --- /dev/null +++ b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuFormulaSuite.scala @@ -0,0 +1,78 @@ +/** + * 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.procfs.simple + +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 org.powerapi.module.PowerChannel +import org.powerapi.module.procfs.ProcMetricsChannel +import scala.concurrent.duration.DurationInt + +trait SimpleCpuFormulaConfigurationMock extends FormulaConfiguration { + override lazy val tdp = 220 + override lazy val tdpFactor = 0.7 +} + +class SimpleCpuFormulaMock(messageBus: MessageBus) + extends CpuFormula(messageBus) + with SimpleCpuFormulaConfigurationMock + +class SimpleCpuFormulaSuite(system: ActorSystem) extends UnitTest(system) { + + implicit val timeout = Timeout(1.seconds) + + def this() = this(ActorSystem("SimpleCpuFormulaSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + val eventBus = new MessageBus + 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.ClockChannel.ClockTick + import PowerChannel.{PowerReport, subscribePowerReport} + import ProcMetricsChannel.{publishUsageReport, TargetUsageRatio} + import org.powerapi.module.PowerUnit + + val muid = UUID.randomUUID() + val target = Process(1) + val targetRatio = TargetUsageRatio(0.4) + val tickMock = ClockTick("test", 25.milliseconds) + val power = 220 * 0.7 * targetRatio.percent + + subscribePowerReport(muid)(eventBus)(testActor) + publishUsageReport(muid, target, targetRatio, tickMock)(eventBus) + + expectMsgClass(classOf[PowerReport]) match { + case PowerReport(_, id, targ, pow, PowerUnit.W, "cpu", tic) if muid == id && target == targ && power == pow && tickMock == tic => assert(true) + case _ => assert(false) + } + } +} diff --git a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala new file mode 100644 index 0000000..5e1aaa5 --- /dev/null +++ b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala @@ -0,0 +1,156 @@ +/** + * 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.procfs.simple + +import java.util.UUID + +import akka.actor.{ActorSystem, Props} +import akka.testkit.{TestActorRef, TestKit} +import akka.util.Timeout +import org.powerapi.UnitTest +import org.powerapi.core.{ MessageBus, OSHelper} + +import scala.concurrent.duration.DurationInt + +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(1) => Some(33 + 2) + 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 SimpleCpuSensorSuite(system: ActorSystem) extends UnitTest(system) { + 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} + + implicit val timeout = Timeout(1.seconds) + + def this() = this(ActorSystem("SimpleCpuSensorSuite")) + + override def afterAll() = { + TestKit.shutdownActorSystem(system) + } + + val eventBus = new MessageBus + + val globalElapsedTime = 43171 + 1 + 24917 + 25883594 + 1160 + 19 + 1477 + 0 + val p1ElapsedTime = 33 + 2 + val p2ElapsedTime = 10 + 5 + val p3ElapsedTime = 3 + 5 + val appElapsedTime = p2ElapsedTime + p3ElapsedTime + + val cpuSensor = TestActorRef(Props(classOf[CpuSensor], eventBus, new OSHelperMock()), "simple-CpuSensor")(system) + + "A simple CpuSensor" should "refresh its cache after each processed message" in { + val muid = UUID.randomUUID() + val processTarget = Process(1) + + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.cache shouldBe empty + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.handleMonitorTick(MonitorTick("test", muid, processTarget, ClockTick("test", 25.milliseconds))) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.cache should equal( + Map(CacheKey(muid, processTarget) -> (p1ElapsedTime, globalElapsedTime)) + ) + } + + it should "handle a Process target or an Application target" in { + val oldP1ElapsedTime = p1ElapsedTime / 2 + val oldAppElapsedTime = appElapsedTime / 2 + val oldGlobalElapsedTime = globalElapsedTime / 2 + + val muid = UUID.randomUUID() + val processTarget = Process(1) + val appTarget = Application("app") + + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.refreshCache(CacheKey(muid, processTarget), (oldP1ElapsedTime, oldGlobalElapsedTime)) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.refreshCache(CacheKey(muid, appTarget), (oldAppElapsedTime, oldGlobalElapsedTime)) + + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.handleMonitorTick(MonitorTick("test", muid, processTarget, ClockTick("test", 25.milliseconds))) should equal( + TargetUsageRatio((p1ElapsedTime - oldP1ElapsedTime).toDouble / (globalElapsedTime - oldGlobalElapsedTime)) + ) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.handleMonitorTick(MonitorTick("test", muid, appTarget, ClockTick("test", 25.milliseconds))) should equal( + TargetUsageRatio((appElapsedTime - oldAppElapsedTime).toDouble / (globalElapsedTime - oldGlobalElapsedTime)) + ) + } + + it should "not handle an All target" in { + val muid = UUID.randomUUID() + + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.handleMonitorTick(MonitorTick("test", muid, All, ClockTick("test", 25.milliseconds))) should equal( + TargetUsageRatio(0) + ) + } + + it should "process a MonitorTicks message and then publish a UsageReport" in { + import org.powerapi.core.MonitorChannel.publishMonitorTick + import org.powerapi.module.procfs.ProcMetricsChannel.subscribeSimpleUsageReport + + val oldP1ElapsedTime = p1ElapsedTime / 2 + val oldAppElapsedTime = appElapsedTime / 2 + val oldGlobalElapsedTime = globalElapsedTime / 2 + + val muid1 = UUID.randomUUID() + val muid2 = UUID.randomUUID() + val tickMock = ClockTick("test", 25.milliseconds) + + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.refreshCache(CacheKey(muid1, Process(1)), (oldP1ElapsedTime, oldGlobalElapsedTime)) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.refreshCache(CacheKey(muid2, Process(1)), (oldP1ElapsedTime, oldGlobalElapsedTime)) + cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.refreshCache(CacheKey(muid2, Application("app")), (oldAppElapsedTime, oldGlobalElapsedTime)) + + val processRatio = TargetUsageRatio((p1ElapsedTime - oldP1ElapsedTime).toDouble / (globalElapsedTime - oldGlobalElapsedTime)) + val appRatio = TargetUsageRatio((appElapsedTime - oldAppElapsedTime).toDouble / (globalElapsedTime - oldGlobalElapsedTime)) + + subscribeSimpleUsageReport(eventBus)(testActor) + + publishMonitorTick(muid1, Process(1), tickMock)(eventBus) + expectMsgClass(classOf[UsageReport]) match { + case UsageReport(_, id, Process(1), processr, _, _) if muid1 == id && processRatio == processr => assert(true) + case _ => assert(false) + } + publishMonitorTick(muid2, Process(1), tickMock)(eventBus) + expectMsgClass(classOf[UsageReport]) match { + case UsageReport(_, id, Process(1), processr, _, _) if muid2 == id && processRatio == processr => assert(true) + case _ => assert(false) + } + publishMonitorTick(muid2, Application("app"), tickMock)(eventBus) + expectMsgClass(classOf[UsageReport]) match { + case UsageReport(_, id, Application("app"), appr, _, _) if id == muid2 && appRatio == appr => assert(true) + case _ => assert(false) + } + } +}