Skip to content

Commit

Permalink
refactoring: Move methods from Procfs sensors inside the LinuxHelper.
Browse files Browse the repository at this point in the history
Move the methods useful to read the cpu time inside the LinuxHelper.

refactoring: Improve the method encapsulation.

typo:  Fix a typo in @author tag.
  • Loading branch information
mcolmant committed Nov 27, 2014
1 parent fa9ca00 commit 3f88de3
Show file tree
Hide file tree
Showing 18 changed files with 349 additions and 233 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/org/powerapi/core/Configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -29,7 +29,7 @@ package org.powerapi.module.procfs
*
* @author Maxime Colmant <[email protected]>
*/
object FileControl {
object FileHelper {
def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B = {
try {
f(resource)
Expand Down
160 changes: 151 additions & 9 deletions src/main/scala/org/powerapi/core/OSHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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 <[email protected]>
* @author Maxime Colmant <[email protected]>
*/
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.
*
Expand All @@ -52,23 +61,90 @@ 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 <[email protected]>
*/
trait LogicalCoresConfiguration {
self: Configuration =>

lazy val cores = load { _.getInt("powerapi.hardware.cores") } match {
case ConfigValue(nbCores) => nbCores
case _ => 0
}
}

/**
* Linux special helper.
*
* @author Maxime Colmant <[email protected]>
*/
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] = {
Expand All @@ -78,14 +154,80 @@ 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.
*/
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])
}
}
17 changes: 6 additions & 11 deletions src/main/scala/org/powerapi/module/procfs/ProcMetricsChannel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,19 @@ 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 <aurelien@[email protected]>
* @author Maxime Colmant <[email protected]>
*/
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)

Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import scala.collection.JavaConversions
/**
* CPU formula configuration.
*
* @author Aurélien Bourdon <aurelien@[email protected]>
* @author Aurélien Bourdon <aurelien.[email protected]>
* @author Maxime Colmant <[email protected]>
*/
trait FormulaConfiguration extends org.powerapi.module.procfs.simple.FormulaConfiguration {
Expand Down Expand Up @@ -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 <aurelien@[email protected]>
* @author Aurélien Bourdon <aurelien.[email protected]>
* @author Maxime Colmant <[email protected]>
*/
class CpuFormula(eventBus: MessageBus) extends FormulaComponent(eventBus) with FormulaConfiguration {
Expand Down
Loading

0 comments on commit 3f88de3

Please sign in to comment.