Skip to content

Commit

Permalink
Merge pull request #51 from joular/virtualmachine
Browse files Browse the repository at this point in the history
Virtualmachine
  • Loading branch information
adelnoureddine authored Jun 3, 2024
2 parents 4661fcc + 1e443af commit a2abf7c
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 62 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Detailed documentation (including user and reference guides) are available at: [
## :rocket: Features

- Monitor power consumption of CPU and GPU of PC/servers
- Monitor power consumption inside virtual machines
- Monitor power consumption of individual processes in GNU/Linux
- Expose power consumption to the terminal and CSV files
- Provides a systemd service (daemon) to continuously monitor power of devices
Expand All @@ -22,13 +23,19 @@ Detailed documentation (including user and reference guides) are available at: [
PowerJoular monitors the following platforms:
- :computer: PC/Servers using a RAPL supported Intel processor (since Sandy Bridge) or a RAPL supported AMD processor (Ryzen or EPYC), and optionally an Nvidia graphic card.
- :radio: Raspberry Pi devices (multiple models) and Asus Tinker Board.
- :computer: Inside virtual machines in all supported host platforms.

In all platforms, PowerJoular works currently only on GNU/Linux.

On PC/Servers, PowerJoular uses powercap Linux interface to read Intel RAPL (Running Average Power Limit) energy consumption.

PowerJoular supports RAPL package domain (core, including integrated graphics, and dram), and for more recent processors, we support Psys package (which covers the energy consumption of the entire SoC).

On virtual machines, PowerJoular requires two steps:
- Installing PowerJoular itself or another power monitoring tool in the host machine.
Then monitoring the virtual machine power consumption every second and writing it to a file (to be shared with the guest VM).
- Installing PowerJoular in the guest VM, then running PowerJoular while specifying the path of the power file shared with the host and its format.

On Raspberry Pi and Asus Tinker Board, PowerJoular uses its own research-based empirical regression models to estimate the power consumption of the ARM processor.

The supported list of Raspberry Pi and Asus Tinker Board models are listed below.
Expand Down Expand Up @@ -74,6 +81,8 @@ The following options are available:
- ```-t```: print energy data to the terminal
- ```-d```: print debug info to the terminal
- ```-l```: use linear regression models (less accurate than the default polynomial models) for Raspberry Pi energy models
- ```-m```: specify a filename for the power consumption of the virtual machine
- ```-s```: specify the format of the VM power, either ```powerjoular``` format (generated with the ```-o``` option: 3 columns csv file with the 3rd containing the power consumption the VM), or ```watts``` format (1 column containing just the power consumption of the VM)

You can mix options, i.e., ```powerjoular -tp 144``` will monitor PID 144 and will print to the terminal.

Expand Down
150 changes: 88 additions & 62 deletions src/powerjoular.adb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ with OS_Utils; use OS_Utils;
with Nvidia_SMI; use Nvidia_SMI;
with Raspberry_Pi_CPU_Formula; use Raspberry_Pi_CPU_Formula;
with CPU_STAT_App; use CPU_STAT_App;
with Virtual_Machine; use Virtual_Machine;

procedure Powerjoular is
-- Power variables
Expand Down Expand Up @@ -77,9 +78,12 @@ procedure Powerjoular is
-- Platform name
Platform_Name : String := Get_Platform_Name;

-- CSV filenames
CSV_Filename : Unbounded_String; -- CSV filename for entire CPU power data
PID_Or_App_CSV_Filename : Unbounded_String; -- CSV filename for monitored PID or application CPU power data
-- CSV filenames
CSV_Filename : Unbounded_String; -- CSV filename for entire CPU power data
PID_Or_App_CSV_Filename : Unbounded_String; -- CSV filename for monitored PID or application CPU power data
VM_File_Name : Unbounded_String; -- Filename containing the power consumption of the VM
VM_Power_Format : Unbounded_String; -- Format of the VM power data (currently powerjoualr or watts)
Monitor_VM : Boolean := False;

-- Settings
Show_Terminal : Boolean := False; -- Show power data on terminal
Expand Down Expand Up @@ -116,36 +120,43 @@ begin

-- Loop over command line options
loop
case Getopt ("h v t d f: p: a: o: u l") is
when 'h' => -- Show help
Show_Help;
return;
when 'v' => -- Show help
Show_Version;
return;
when 't' => -- Show power data on terminal
Show_Terminal := True;
when 'd' => -- Show debug info on terminal
Show_Debug := True;
when 'p' => -- Monitor a particular PID
-- PID_Number := Integer'Value (Parameter);
CPU_PID_Monitor.PID_Number := Integer'Value (Parameter);
Monitor_PID := True;
when 'a' => -- Monitor a particular application by its name
CPU_App_Monitor.App_Name := To_Unbounded_String (Parameter);
Monitor_App := True;
when 'f' => -- Specifiy a filename for CSV file (append data)
CSV_Filename := To_Unbounded_String (Parameter);
Print_File := True;
when 'o' => -- Specifiy a filename for CSV file (overwrite data)
CSV_Filename := To_Unbounded_String (Parameter);
Print_File := True;
Overwrite_Data := True;
when 'l' => -- Use linear regression model instead of polynomial models
Algorithm_Name := To_Unbounded_String ("linear");
when others =>
exit;
end case;
case Getopt ("h v t d f: p: a: o: u l m: s:") is
when 'h' => -- Show help
Show_Help;
return;
when 'v' => -- Show help
Show_Version;
return;
when 't' => -- Show power data on terminal
Show_Terminal := True;
when 'd' => -- Show debug info on terminal
Show_Debug := True;
when 'p' => -- Monitor a particular PID
-- PID_Number := Integer'Value (Parameter);
CPU_PID_Monitor.PID_Number := Integer'Value (Parameter);
Monitor_PID := True;
when 'a' => -- Monitor a particular application by its name
CPU_App_Monitor.App_Name :=
To_Unbounded_String (Parameter);
Monitor_App := True;
when 'f' => -- Specifiy a filename for CSV file (append data)
CSV_Filename := To_Unbounded_String (Parameter);
Print_File := True;
when 'o' => -- Specifiy a filename for CSV file (overwrite data)
CSV_Filename := To_Unbounded_String (Parameter);
Print_File := True;
Overwrite_Data := True;
when 'l' => -- Use linear regression model instead of polynomial models
Algorithm_Name := To_Unbounded_String ("linear");
when 'm' => -- Specify a filename for the power consumption of the VM
VM_File_Name := To_Unbounded_String (Parameter);
Monitor_VM := True;
when 's' => -- Specify a data format for the VM power
VM_Power_Format := To_Unbounded_String (Parameter);
Monitor_VM := True;
when others =>
exit;
end case;
end loop;

if (Argument_Count = 0) then
Expand Down Expand Up @@ -258,35 +269,50 @@ begin
-- Calculate entire CPU utilization
CPU_Utilization := (Long_Float (CPU_CCI_After.cbusy) - Long_Float (CPU_CCI_Before.cbusy)) / (Long_Float (CPU_CCI_After.ctotal) - Long_Float (CPU_CCI_Before.ctotal));

if Check_Raspberry_Pi_Supported_System (Platform_Name) then
-- Calculate power consumption for Raspberry
CPU_Power := Calculate_CPU_Power (CPU_Utilization, Platform_Name, To_String (Algorithm_Name));
Total_Power := CPU_Power;
end if;

if Check_Intel_Supported_System (Platform_Name) then
-- Calculate Intel RAPL energy consumption
RAPL_Energy := RAPL_After.total_energy - RAPL_Before.total_energy;

if RAPL_Before.total_energy > RAPL_After.total_energy then
-- energy has wrapped
if RAPL_Before.psys_supported then
RAPL_Energy := RAPL_Energy + RAPL_Before.psys_max_energy_range;
elsif RAPL_Before.Pkg_Supported then
RAPL_Energy := RAPL_Energy + RAPL_Before.pkg_max_energy_range;
end if;
end if;

if RAPL_Before.Pkg_Supported and RAPL_Before.Dram_supported then
if RAPL_Before.dram > RAPL_After.dram then
-- dram has wrapped
RAPL_Energy := RAPL_Energy + RAPL_Before.dram_max_energy_range;
end if;
end if;

CPU_Power := RAPL_Energy;
Total_Power := CPU_Power;
end if;
--Calculate VM consumption
if Monitor_VM then
CPU_Power := Read_VM_Power(VM_File_Name, VM_Power_Format);
Total_Power := CPU_Power;
else
if Check_Raspberry_Pi_Supported_System (Platform_Name) then
-- Calculate power consumption for Raspberry
CPU_Power :=
Calculate_CPU_Power
(CPU_Utilization, Platform_Name,
To_String (Algorithm_Name));
Total_Power := CPU_Power;
end if;

if Check_Intel_Supported_System (Platform_Name) then
-- Calculate Intel RAPL energy consumption
RAPL_Energy :=
RAPL_After.total_energy - RAPL_Before.total_energy;

if RAPL_Before.total_energy > RAPL_After.total_energy then
-- energy has wrapped
if RAPL_Before.psys_supported then
RAPL_Energy :=
RAPL_Energy + RAPL_Before.psys_max_energy_range;
elsif RAPL_Before.pkg_supported then
RAPL_Energy :=
RAPL_Energy + RAPL_Before.pkg_max_energy_range;
end if;
end if;

if RAPL_Before.pkg_supported and RAPL_Before.dram_supported
then
if RAPL_Before.dram > RAPL_After.dram then
-- dram has wrapped
RAPL_Energy :=
RAPL_Energy + RAPL_Before.dram_max_energy_range;
end if;
end if;

CPU_Power := RAPL_Energy;
Total_Power := CPU_Power;
end if;

end if;

if Nvidia_Supported then
-- Calculate GPU power consumption
Expand Down
137 changes: 137 additions & 0 deletions src/virtual_machine.adb
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
--
-- Copyright (c) 2020-2024, Adel Noureddine, Université de Pau et des Pays de l'Adour.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the
-- GNU General Public License v3.0 only (GPL-3.0-only)
-- which accompanies this distribution, and is available at:
-- https://www.gnu.org/licenses/gpl-3.0.en.html
--
-- Author : Axel Terrier
-- Contributors : Adel Noureddine
--

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Directories; use Ada.Directories;
with Ada.Text_IO; use Ada.Text_IO;
with GNAT.OS_Lib; use GNAT.OS_Lib;
with GNAT.String_Split; use GNAT;
with Ada.Exceptions; use Ada.Exceptions;

package body Virtual_Machine is

-- Read the exported data in PowerJoular format
function Read_PowerJoular (File_Path : String) return Long_Float is
F : File_Type; -- File handle
Line : Unbounded_String;
Subs : String_Split.Slice_Set; -- Used to slice the read data from stat file
Seps : constant String := ","; -- Seperator (space) for slicing string
Power_Value : Long_Float;
begin
Open (F, In_File, File_Path);
Line := To_Unbounded_String (Get_Line (F)); -- Read data. We only need the first line of the file
Close (F);

-- Ensure the line is not empty before processing
if Line /= To_Unbounded_String ("") then
-- Processes the first line obtained
String_Split.Create (S => Subs, -- Store sliced data in Subs
From => To_String (Line),
Separators => Seps, -- Separator
Mode => String_Split.Multiple);

-- Converts the value of the third column (power consumption of PID or app, so of VM) into a float
Power_Value := Long_Float'Value (String_Split.Slice (Subs, 3));

return Power_Value;
else
return 0.0; -- Return 0 is file is empty
end if;
exception
when others =>
Put_Line ("The file cannot be found, check the path");
OS_Exit (0);
end Read_PowerJoular;

function Read_Watts (File_Path : String) return Long_Float is
F : File_Type;
Line : Unbounded_String;
Result : Long_Float := 0.0;
begin
Open (F, In_File, File_Path);
Line := To_Unbounded_String (Get_Line (F)); -- Read data. We only need the first line of the file
Close (F);

-- Clean the string and keep only numbers and dots
declare
Temp : String := To_String (Line);
Clean : String (1 .. Temp'Length) := (others => '0');
j : Natural := 1;
begin
for i in Temp'Range loop
if ('0' <= Temp (i) and Temp (i) <= '9') or Temp (i) = '.'
then
Clean (j) := Temp (i);
j := j + 1;
end if;
end loop;
Line := To_Unbounded_String (Clean (1 .. j - 1));
end;

-- Attempted conversion
begin
Result := Long_Float'Value (To_String (Line));
exception
when E : others =>
Put_Line ("Failed to convert to long float: " & Exception_Message (E));
OS_Exit (0);
end;

return Result;
exception
when E : others =>
Put_Line ("Error after reading line: " & Exception_Message (E));
OS_Exit (0);
end Read_Watts;

function Read_VM_Power (File_Name : Unbounded_String; Power_Format : Unbounded_String) return Long_Float is
VM_File_Name : String := To_String(File_Name);
VM_Power_Format : String := To_String(Power_Format);
Power : Long_Float;

-- Customized exceptions
Invalid_File_Name_Exception : exception;
Invalid_Format_Exception : exception;
begin
-- Check if file name parameter exist
if VM_File_Name = "" then
raise Invalid_File_Name_Exception;
end if;

-- Check file existence
if not Exists (VM_File_Name) then
raise Invalid_File_Name_Exception;
end if;

-- Iteration on supported formats
if VM_Power_Format = ("powerjoular") then
Power := Read_PowerJoular (VM_File_Name);
else
if VM_Power_Format = ("watts") then
Power := Read_Watts (VM_File_Name);
else
raise Invalid_Format_Exception; -- Format not supported
end if;
end if;

return Power;

exception
when Invalid_File_Name_Exception =>
Put_Line ("Error: The file name is invalid..");
OS_Exit (0);
when Invalid_Format_Exception =>
Put_Line ("Error: The specified power format is not supported.");
OS_Exit (0);
end Read_VM_Power;

end Virtual_Machine;
20 changes: 20 additions & 0 deletions src/virtual_machine.ads
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--
-- Copyright (c) 2020-2024, Adel Noureddine, Université de Pau et des Pays de l'Adour.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the
-- GNU General Public License v3.0 only (GPL-3.0-only)
-- which accompanies this distribution, and is available at:
-- https://www.gnu.org/licenses/gpl-3.0.en.html
--
-- Author : Axel Terrier
-- Contributors : Adel Noureddine
--

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package Virtual_Machine is

-- Read virtual machine power data from the shared file and returns the power consumption of the entire VM
function Read_VM_Power(File_Name : Unbounded_String; Power_Format : Unbounded_String) return Long_Float;

end Virtual_Machine;

0 comments on commit a2abf7c

Please sign in to comment.