From 3f88de3b0102231b0fd0ab5a520774f930274ebc Mon Sep 17 00:00:00 2001 From: mcolmant Date: Thu, 27 Nov 2014 22:21:26 +0100 Subject: [PATCH] refactoring: Move methods from Procfs sensors inside the LinuxHelper. Move the methods useful to read the cpu time inside the LinuxHelper. refactoring: Improve the method encapsulation. typo: Fix a typo in @author tag. --- README.md | 1 + build.sbt | 2 + .../org/powerapi/core/Configuration.scala | 2 +- .../FileHelper.scala} | 4 +- .../scala/org/powerapi/core/OSHelper.scala | 160 +++++++++++++++++- .../module/procfs/ProcMetricsChannel.scala | 17 +- .../module/procfs/dvfs/CpuFormula.scala | 4 +- .../module/procfs/dvfs/CpuSensor.scala | 66 +------- .../module/procfs/simple/CpuFormula.scala | 4 +- .../module/procfs/simple/CpuSensor.scala | 85 +--------- src/test/resources/log4j2.xml | 13 ++ .../resources/proc/1/task/1000/to-include | 0 .../resources/proc/1/task/1001/to-include | 0 .../org/powerapi/core/OSHelperSuite.scala | 113 +++++++++++++ .../org/powerapi/module/FormulaSuite.scala | 5 +- .../procfs/dvfs/DvfsCpuFormulaSuite.scala | 12 +- .../procfs/dvfs/DvfsCpuSensorSuite.scala | 54 +++--- .../procfs/simple/SimpleCpuSensorSuite.scala | 40 ++--- 18 files changed, 349 insertions(+), 233 deletions(-) rename src/main/scala/org/powerapi/{module/procfs/FileControl.scala => core/FileHelper.scala} (95%) create mode 100644 src/test/resources/log4j2.xml create mode 100644 src/test/resources/proc/1/task/1000/to-include create mode 100644 src/test/resources/proc/1/task/1001/to-include create mode 100644 src/test/scala/org/powerapi/core/OSHelperSuite.scala diff --git a/README.md b/README.md index dd4c6f9..ac54f56 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/core/Configuration.scala b/src/main/scala/org/powerapi/core/Configuration.scala index f553618..49570f5 100644 --- a/src/main/scala/org/powerapi/core/Configuration.scala +++ b/src/main/scala/org/powerapi/core/Configuration.scala @@ -50,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/module/procfs/FileControl.scala b/src/main/scala/org/powerapi/core/FileHelper.scala similarity index 95% rename from src/main/scala/org/powerapi/module/procfs/FileControl.scala rename to src/main/scala/org/powerapi/core/FileHelper.scala index 83db3ba..461e25d 100644 --- a/src/main/scala/org/powerapi/module/procfs/FileControl.scala +++ b/src/main/scala/org/powerapi/core/FileHelper.scala @@ -20,7 +20,7 @@ * If not, please consult http://www.gnu.org/licenses/agpl-3.0.html. */ -package org.powerapi.module.procfs +package org.powerapi.core /** * Implement the Loan's pattern for closing automatically a resource. @@ -29,7 +29,7 @@ package org.powerapi.module.procfs * * @author Maxime Colmant */ -object FileControl { +object FileHelper { def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B = { try { f(resource) diff --git a/src/main/scala/org/powerapi/core/OSHelper.scala b/src/main/scala/org/powerapi/core/OSHelper.scala index 6e854f1..991c413 100644 --- a/src/main/scala/org/powerapi/core/OSHelper.scala +++ b/src/main/scala/org/powerapi/core/OSHelper.scala @@ -22,8 +22,6 @@ */ package org.powerapi.core -import java.io.File - /** * This is not a monitoring target. It's an internal wrapper for the Thread IDentifier. * @@ -33,6 +31,17 @@ import java.io.File */ 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. * @@ -52,6 +61,37 @@ trait OSHelper { * @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 +} + +/** + * Number of logical cores / Configuration. + * + * @author Maxime Colmant + */ +trait LogicalCoresConfiguration { + self: Configuration => + + lazy val cores = load { _.getInt("powerapi.hardware.cores") } match { + case ConfigValue(nbCores) => nbCores + case _ => 0 + } } /** @@ -59,16 +99,52 @@ trait OSHelper { * * @author Maxime Colmant */ -object LinuxHelper extends OSHelper with Configuration { +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 - val PSFormat = """^\s*(\d+)""".r + 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") } match { - case ConfigValue(path) if path.contains("$pid") => path - case _ => "/proc/$pid/task" + 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] = { @@ -78,9 +154,9 @@ object LinuxHelper extends OSHelper with Configuration { } def getThreads(process: Process): List[Thread] = { - val pidDirectory = new File(taskPath.replace("$pid", s"${process.pid}")) + val pidDirectory = new File(taskPath.replace("%?pid", s"${process.pid}")) - if(pidDirectory.exists && pidDirectory.isDirectory) { + if (pidDirectory.exists && pidDirectory.isDirectory) { /** * The pid is removed because it corresponds to the main thread. */ @@ -88,4 +164,70 @@ object LinuxHelper extends OSHelper with Configuration { } 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/module/procfs/ProcMetricsChannel.scala b/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala index 4ca5413..8de0dd1 100644 --- a/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala +++ b/src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala @@ -26,13 +26,12 @@ import java.util.UUID import akka.actor.ActorRef import org.powerapi.core.ClockChannel.ClockTick -import org.powerapi.core.{MessageBus, Target} +import org.powerapi.core.{MessageBus, Target, TimeInStates} import org.powerapi.module.{SensorReport, SensorChannel} /** * ProcMetricsChannel channel and messages. * - * @author Aurélien Bourdon * @author Maxime Colmant */ object ProcMetricsChannel extends SensorChannel { @@ -40,10 +39,6 @@ object ProcMetricsChannel extends SensorChannel { /** * Wrapper classes. */ - case class TimeInStates(times: Map[Int, Long]) { - def -(that: TimeInStates) = - TimeInStates((for ((frequency, time) <- times) yield (frequency, time - that.times.getOrElse(frequency, 0: Long))).toMap) - } case class TargetUsageRatio(percent: Double = 0) case class CacheKey(muid: UUID, target: Target) @@ -58,11 +53,11 @@ object ProcMetricsChannel extends SensorChannel { * @param tick: tick origin. */ case class UsageReport(topic: String, - muid: UUID, - target: Target, - targetRatio: TargetUsageRatio, - timeInStates: TimeInStates = TimeInStates(Map()), - tick: ClockTick) extends SensorReport + muid: UUID, + target: Target, + targetRatio: TargetUsageRatio, + timeInStates: TimeInStates = TimeInStates(Map()), + tick: ClockTick) extends SensorReport /** * Topic for communicating with the Formula actors. diff --git a/src/main/scala/org/powerapi/module/procfs/dvfs/CpuFormula.scala b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuFormula.scala index 94a53ae..10c3395 100644 --- a/src/main/scala/org/powerapi/module/procfs/dvfs/CpuFormula.scala +++ b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuFormula.scala @@ -32,7 +32,7 @@ import scala.collection.JavaConversions /** * CPU formula configuration. * - * @author Aurélien Bourdon + * @author Aurélien Bourdon * @author Maxime Colmant */ trait FormulaConfiguration extends org.powerapi.module.procfs.simple.FormulaConfiguration { @@ -61,7 +61,7 @@ trait FormulaConfiguration extends org.powerapi.module.procfs.simple.FormulaConf * @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 Aurélien Bourdon * @author Maxime Colmant */ class CpuFormula(eventBus: MessageBus) extends FormulaComponent(eventBus) with FormulaConfiguration { diff --git a/src/main/scala/org/powerapi/module/procfs/dvfs/CpuSensor.scala b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuSensor.scala index 828b8e4..192224e 100644 --- a/src/main/scala/org/powerapi/module/procfs/dvfs/CpuSensor.scala +++ b/src/main/scala/org/powerapi/module/procfs/dvfs/CpuSensor.scala @@ -23,33 +23,7 @@ package org.powerapi.module.procfs.dvfs import org.powerapi.core.{MessageBus, OSHelper} -import org.powerapi.module.procfs.{ProcMetricsChannel, FileControl} - -/** - * CPU sensor configuration. - * - * @author Aurélien Bourdon - * @author Maxime Colmant - */ -trait SensorConfiguration extends org.powerapi.core.Configuration { - import org.powerapi.core.ConfigValue - - /** - * OS cores number (can be logical). - */ - lazy val cores = load { _.getInt("powerapi.hardware.cores") } match { - case ConfigValue(nbCores) => nbCores - case _ => 0 - } - - /** - * 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" - } -} +import org.powerapi.module.procfs.ProcMetricsChannel /** * CPU sensor component that collects data from a /proc and /sys directories @@ -57,10 +31,10 @@ trait SensorConfiguration extends org.powerapi.core.Configuration { * * @see http://www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html * - * @author Aurélien Bourdon + * @author Aurélien Bourdon * @author Maxime Colmant */ -class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends org.powerapi.module.procfs.simple.CpuSensor(eventBus, osHelper) with SensorConfiguration { +class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends org.powerapi.module.procfs.simple.CpuSensor(eventBus, osHelper) { import org.powerapi.core.MonitorChannel.MonitorTick import ProcMetricsChannel.publishUsageReport @@ -68,45 +42,17 @@ class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends org.powerapi.m * Delegate class to deal with time spent within each CPU frequencies. */ class Frequencies { - import java.io.IOException - import FileControl.using - import ProcMetricsChannel.{CacheKey, TimeInStates} - import scala.io.Source + import org.powerapi.core.TimeInStates + import ProcMetricsChannel.CacheKey - // time_in_state line format: frequency time - private val TimeInStateFormat = """(\d+)\s+(\d+)""".r lazy val cache = collection.mutable.Map[CacheKey, TimeInStates]() - def timeInStates(): Map[Int, Long] = { - val result = collection.mutable.HashMap[Int, 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.toInt -> (t.toLong + (result.getOrElse(freq.toInt, 0l)))) - case _ => log.warning("unable to parse line {} from file {}", line, timeInStatePath) - } - } - }) - } - catch { - case ioe: IOException => log.warning("i/o exception: {}", ioe.getMessage); - } - } - - result.toMap[Int, Long] - } - def refreshCache(key: CacheKey, now: TimeInStates): Unit = { cache += (key -> now) } def handleMonitorTick(tick: MonitorTick): TimeInStates = { - val now = TimeInStates(timeInStates) + val now = osHelper.getTimeInStates() val key = CacheKey(tick.muid, tick.target) val old = cache.getOrElse(key, now) refreshCache(key, now) diff --git a/src/main/scala/org/powerapi/module/procfs/simple/CpuFormula.scala b/src/main/scala/org/powerapi/module/procfs/simple/CpuFormula.scala index 8eb0e33..770a251 100644 --- a/src/main/scala/org/powerapi/module/procfs/simple/CpuFormula.scala +++ b/src/main/scala/org/powerapi/module/procfs/simple/CpuFormula.scala @@ -29,7 +29,7 @@ import org.powerapi.module.procfs.ProcMetricsChannel /** * CPU formula configuration. * - * @author Aurélien Bourdon + * @author Aurélien Bourdon * @author Maxime Colmant */ trait FormulaConfiguration extends org.powerapi.core.Configuration { @@ -62,7 +62,7 @@ trait FormulaConfiguration extends org.powerapi.core.Configuration { * * @see http://en.wikipedia.org/wiki/Thermal_design_power * - * @author Aurélien Bourdon + * @author Aurélien Bourdon * @author Maxime Colmant */ class CpuFormula(eventBus: MessageBus) extends FormulaComponent(eventBus) with FormulaConfiguration { diff --git a/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala b/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala index 869dca4..f949950 100644 --- a/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala +++ b/src/main/scala/org/powerapi/module/procfs/simple/CpuSensor.scala @@ -26,44 +26,16 @@ import org.powerapi.core.{MessageBus, OSHelper} import org.powerapi.module.SensorComponent import org.powerapi.module.procfs.ProcMetricsChannel -/** - * CPU sensor configuration. - * - * @author Aurélien Bourdon - * @author Maxime Colmant - */ -trait SensorConfiguration extends org.powerapi.core.Configuration { - import org.powerapi.core.ConfigValue - - /** - * 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" - } -} - /** * 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 Aurélien Bourdon * @author Maxime Colmant */ -class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponent(eventBus) with SensorConfiguration { +class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponent(eventBus) { import org.powerapi.core.MonitorChannel.MonitorTick import ProcMetricsChannel.publishUsageReport @@ -72,69 +44,24 @@ class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponen * and providing the target CPU percent usage. */ class TargetRatio { - import java.io.IOException import org.powerapi.core.{All, Application, Process} - import org.powerapi.module.procfs.FileControl.using import ProcMetricsChannel.CacheKey - import scala.io.Source - - private val GlobalStatFormat = """cpu\s+([\d\s]+)""".r /** * Internal cache, used to get the diff between two ClockTick. */ lazy val cache = collection.mutable.Map[CacheKey, (Long, Long)]() - def globalElapsedTime(): 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) => times.split("\\s").slice(0, 8).foldLeft(0: Long) { - (acc, x) => acc + x.toLong - } - case _ => log.warning("unable to parse line from {}", globalStatPath); 0l - } - - Some(time) - }) - } - catch { - case ioe: IOException => log.warning("i/o exception: {}", ioe.getMessage); None - } - } - - def processElapsedTime(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.warning("i/o exception: {}", ioe.getMessage); None - } - } - def refreshCache(key: CacheKey, now: (Long, Long)): Unit = { cache += (key -> now) } def handleProcessTarget(process: Process): (Long, Long) = { - lazy val processTime: Long = processElapsedTime(process) match { + lazy val processTime: Long = osHelper.getProcessCpuTime(process) match { case Some(value) => value case _ => 0l } - lazy val globalTime: Long = globalElapsedTime() match { + lazy val globalTime: Long = osHelper.getGlobalCpuTime() match { case Some(value) => value case _ => 0l } @@ -145,13 +72,13 @@ class CpuSensor(eventBus: MessageBus, osHelper: OSHelper) extends SensorComponen def handleApplicationTarget(application: Application): (Long, Long) = { lazy val processTime: Long = osHelper.getProcesses(application).foldLeft(0: Long) { (acc, process: Process) => { - processElapsedTime(process) match { + osHelper.getProcessCpuTime(process) match { case Some(value) => acc + value case _ => acc } } } - lazy val globalTime: Long = globalElapsedTime() match { + lazy val globalTime: Long = osHelper.getGlobalCpuTime() match { case Some(value) => value case _ => 0l } diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..c505ac6 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + 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/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 index 986cb43..bea4c39 100644 --- a/src/test/scala/org/powerapi/module/FormulaSuite.scala +++ b/src/test/scala/org/powerapi/module/FormulaSuite.scala @@ -29,10 +29,7 @@ import akka.testkit.{TestActorRef, TestKit} import org.powerapi.UnitTest import org.powerapi.core.{Channel, MessageBus} -object SensorMockChannel extends Channel { - - type M = SensorMockReport - +object SensorMockChannel extends SensorChannel { private val topic = "test" case class SensorMockReport(topic: String, muid: UUID, power: Double) extends SensorReport diff --git a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala index 02e4594..ee9c51d 100644 --- a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala +++ b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuFormulaSuite.scala @@ -74,15 +74,15 @@ class DvfsCpuFormulaSuite(system: ActorSystem) extends UnitTest(system) { } it should "compute correctly the process' power" in { - import org.powerapi.core.Process + import org.powerapi.core.{Process,TimeInStates} import org.powerapi.core.ClockChannel.ClockTick - import ProcMetricsChannel.{UsageReport, TargetUsageRatio, TimeInStates} + import ProcMetricsChannel.{UsageReport, TargetUsageRatio} val topic = "test" val muid = UUID.randomUUID() val target = Process(1) val targetRatio = TargetUsageRatio(0.5) - val timeInStates = TimeInStates(Map(1800002 -> 1, 2100002 -> 2, 2400003 -> 3)) + 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) @@ -99,16 +99,16 @@ class DvfsCpuFormulaSuite(system: ActorSystem) extends UnitTest(system) { } it should "process a SensorReport and then publish a PowerReport" in { - import org.powerapi.core.Process + import org.powerapi.core.{Process,TimeInStates} import org.powerapi.core.ClockChannel.ClockTick import PowerChannel.{PowerReport, subscribePowerReport} - import ProcMetricsChannel.{publishUsageReport, TargetUsageRatio, TimeInStates} + 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(1800002 -> 1, 2100002 -> 2, 2400003 -> 3)) + 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 + diff --git a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuSensorSuite.scala b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuSensorSuite.scala index 194bf64..e6ca067 100644 --- a/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuSensorSuite.scala +++ b/src/test/scala/org/powerapi/module/procfs/dvfs/DvfsCpuSensorSuite.scala @@ -28,34 +28,36 @@ 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 org.powerapi.core.{TimeInStates, MessageBus, OSHelper} import scala.concurrent.duration.DurationInt -trait DvfsCpuSensorConfigurationMock extends SensorConfiguration { - val basepath = getClass.getResource("/").getPath - - override lazy val cores = 4 - override lazy val timeInStatePath = s"$basepath/sys/devices/system/cpu/cpu%?index/cpufreq/stats/time_in_state" -} - -class DvfsCpuSensorMock(messageBus: MessageBus, osHelper: OSHelper) - extends CpuSensor(messageBus, osHelper) - with DvfsCpuSensorConfigurationMock - 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, TimeInStates} + import org.powerapi.module.procfs.ProcMetricsChannel.{CacheKey, UsageReport} implicit val timeout = Timeout(1.seconds) @@ -67,23 +69,7 @@ class DvfsCpuSensorSuite(system: ActorSystem) extends UnitTest(system) { val eventBus = new MessageBus - val cpuSensor = TestActorRef(Props(classOf[DvfsCpuSensorMock], eventBus, new OSHelperMock()), "dvfs-CpuSensor")(system) - - "A TimeInStates case class" should "compute the difference with another one" in { - val timesLeft = TimeInStates(Map(1 -> 10, 2 -> 20, 3 -> 30, 4 -> 15)) - val timesRight = TimeInStates(Map(1 -> 1, 2 -> 2, 3 -> 3, 100 -> 100)) - - (timesLeft - timesRight) should equal(TimeInStates(Map(1 -> 9, 2 -> 18, 3 -> 27, 4 -> 15))) - } - - "Frequencies' time in states" should "be correctly read from the dedicated system file" in { - cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.timeInStates should equal(Map( - 4000000 -> 16, - 3000000 -> 12, - 2000000 -> 8, - 1000000 -> 4 - )) - } + 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() @@ -91,7 +77,7 @@ class DvfsCpuSensorSuite(system: ActorSystem) extends UnitTest(system) { 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(4000000 -> 16, 3000000 -> 12, 2000000 -> 8, 1000000 -> 4))) + Map(CacheKey(muid, processTarget) -> TimeInStates(Map(4000000l -> 16l, 3000000l -> 12l, 2000000l -> 8l, 1000000l -> 4l))) ) } @@ -101,13 +87,13 @@ class DvfsCpuSensorSuite(system: ActorSystem) extends UnitTest(system) { val muid = UUID.randomUUID() val tickMock = ClockTick("test", 25.milliseconds) - val timeInStates = TimeInStates(Map(4000000 -> 6, 3000000 -> 2, 2000000 -> 2, 1000000 -> 2)) + val timeInStates = TimeInStates(Map(4000000l -> 6l, 3000000l -> 2l, 2000000l -> 2l, 1000000l -> 2l)) cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.refreshCache(CacheKey(muid, Process(1)), - TimeInStates(Map(4000000 -> 10, 3000000 -> 10, 2000000 -> 6, 1000000 -> 2)) + TimeInStates(Map(4000000l -> 10l, 3000000l -> 10l, 2000000l -> 6l, 1000000l -> 2l)) ) cpuSensor.underlyingActor.asInstanceOf[CpuSensor].frequencies.refreshCache(CacheKey(muid, Application("app")), - TimeInStates(Map(4000000 -> 10, 3000000 -> 10, 2000000 -> 6, 1000000 -> 2)) + TimeInStates(Map(4000000l -> 10l, 3000000l -> 10l, 2000000l -> 6l, 1000000l -> 2l)) ) subscribeDvfsUsageReport(eventBus)(testActor) diff --git a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala index 54bd3d4..5e1aaa5 100644 --- a/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala +++ b/src/test/scala/org/powerapi/module/procfs/simple/SimpleCpuSensorSuite.scala @@ -28,27 +28,29 @@ 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 org.powerapi.core.{ MessageBus, OSHelper} import scala.concurrent.duration.DurationInt -trait SimpleCpuSensorConfigurationMock extends SensorConfiguration { - val basepath = getClass.getResource("/").getPath - - override lazy val globalStatPath = s"$basepath/proc/stat" - override lazy val processStatPath = s"$basepath/proc/%?pid/stat" -} - -class SimpleCpuSensorMock(messageBus: MessageBus, osHelper: OSHelper) - extends CpuSensor(messageBus, osHelper) - with SimpleCpuSensorConfigurationMock - class OSHelperMock extends OSHelper { - import org.powerapi.core.{Application, Process, Thread} + 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) { @@ -73,17 +75,9 @@ class SimpleCpuSensorSuite(system: ActorSystem) extends UnitTest(system) { val p3ElapsedTime = 3 + 5 val appElapsedTime = p2ElapsedTime + p3ElapsedTime - val cpuSensor = TestActorRef(Props(classOf[SimpleCpuSensorMock], eventBus, new OSHelperMock()), "simple-CpuSensor")(system) - - "A simple CpuSensor" should "read global elapsed time from a given dedicated system file" in { - cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.globalElapsedTime should equal(Some(globalElapsedTime)) - } - - it should "read process elapsed time from a given dedicated system file" in { - cpuSensor.underlyingActor.asInstanceOf[CpuSensor].targetRatio.processElapsedTime(Process(1)) should equal(Some(p1ElapsedTime)) - } + val cpuSensor = TestActorRef(Props(classOf[CpuSensor], eventBus, new OSHelperMock()), "simple-CpuSensor")(system) - it should "refresh its cache after each processed message" in { + "A simple CpuSensor" should "refresh its cache after each processed message" in { val muid = UUID.randomUUID() val processTarget = Process(1)