Skip to content

Commit

Permalink
Merge pull request #36 from benjaminantunes/develop
Browse files Browse the repository at this point in the history
Add handling of multi-threading by PID
  • Loading branch information
adelnoureddine authored Jun 20, 2024
2 parents 27e95f5 + f058ee3 commit a35af66
Showing 1 changed file with 152 additions and 25 deletions.
177 changes: 152 additions & 25 deletions src/cpu_stat_pid.adb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
--
-- Copyright (c) 2020-2023, 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
Expand All @@ -9,47 +8,175 @@
-- Author : Adel Noureddine
--

--with Ada.Text_IO; use Ada.Text_IO;
--with GNAT.String_Split; use GNAT;
--with Ada.Strings.Fixed; use Ada.Strings.Fixed;
--with GNAT.OS_Lib; use GNAT.OS_Lib;

--package body CPU_STAT_PID is

-- procedure Calculate_PID_Time (PID_Data : in out CPU_STAT_PID_Data; Is_Before : in Boolean) is
-- F : File_Type; -- File handle
-- File_Name : constant String := "/proc/" & Trim(Integer'Image(PID_Data.PID_Number), Ada.Strings.Left) & "/stat"; -- File name /proc/pid/stat
-- Subs : String_Split.Slice_Set; -- Used to slice the read data from stat file
-- Seps : constant String := " "; -- Seperator (space) for slicing string
-- Utime : Long_Integer; -- User time
-- Stime : Long_Integer; -- System time
-- begin
-- Open (F, In_File, File_Name);
-- String_Split.Create (S => Subs, -- Store sliced data in Subs
-- From => Get_Line (F), -- Read data to slice. We only need the first line of the stat file
-- Separators => Seps, -- Separator (here space)
-- Mode => String_Split.Multiple);
-- Close (F);

-- -- Reading cpu time from /proc/pid/stat
-- -- We only need utime and stime (user and system time)
-- -- utime is at index 13, stime at index 14 (assuming index starts at 0)
-- -- utime %lu : Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time spent running a virtual CPU, see below), so that applications that are not aware of the guest time field do not lose that time from their calculations.
-- -- stime %lu : Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
-- -- Example of line: 25152 (java) S 12564 1685 1685 0 -1 1077960704 155132 412 478 2 11617 1816 0 0 20 0 61 0 2001362 3813126144 99139 18446744073709551615 4194304 4196724 140736365379696 140736365362368 140056419567211 0 0 4096 16796879 18446744073709551615 0 0 17 2 0 0 3 0 0 6294960 6295616 13131776 140736365387745 140736365388341 140736365388341 140736365391821 0
-- -- fscanf(fp, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu %lu", &cpu_process_data->utime, &cpu_process_data->stime);
-- Utime := Long_Integer'Value (String_Split.Slice (Subs, 14)); -- Index 13 in file. Slice function starts index at 1, so it is 14
-- Stime := Long_Integer'Value (String_Split.Slice (Subs, 15)); -- Index 14 in file. Slice function starts index at 1, so it is 15
-- if (Is_Before) then
-- PID_Data.Before_Time := Utime + Stime; -- Total time
-- else
-- PID_Data.After_Time := Utime + Stime; -- Total time
-- PID_Data.Monitored_Time := PID_Data.After_Time - PID_Data.Before_Time;
-- end if;
-- exception
-- when others =>
-- Put_Line ("Error reading " & File_Name & " file");
-- OS_Exit (0);
-- end;

--end CPU_STAT_PID;



--
--
--
-- Adding feature : Measuring a PID energy consumption has to consider also threads generated by this PID.
-- Multi-threading is a common feature used by many programs.
-- Feature added by : Benjamin Antunes, PhD Student at Université Clermont Auvergne, LIMOS.
-- Original Author of Powerjoular : Adel Noureddine.

with Ada.Text_IO; use Ada.Text_IO;
with GNAT.String_Split; use GNAT;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with GNAT.OS_Lib; use GNAT.OS_Lib;
with GNAT.Expect; use GNAT.Expect;
with GNAT.String_Split; use GNAT;
with Ada.IO_Exceptions;
with Ada.Exceptions; use Ada.Exceptions;


package body CPU_STAT_PID is

procedure Calculate_PID_Time (PID_Data : in out CPU_STAT_PID_Data; Is_Before : in Boolean) is
F : File_Type; -- File handle
File_Name : constant String := "/proc/" & Trim(Integer'Image(PID_Data.PID_Number), Ada.Strings.Left) & "/stat"; -- File name /proc/pid/stat
Subs : String_Split.Slice_Set; -- Used to slice the read data from stat file
Seps : constant String := " "; -- Seperator (space) for slicing string
Utime : Long_Integer; -- User time
Stime : Long_Integer; -- System time
-- This helper function retrieves the CPU time for a specific TID of a given PID.
function Get_TID_Time(PID : Positive; TID : Positive) return Long_Integer is
F : File_Type;
File_Name : constant String := "/proc/" & Trim(Integer'Image(PID), Ada.Strings.Left) & "/task/" & Trim(Integer'Image(TID), Ada.Strings.Left) & "/stat";
Subs : String_Split.Slice_Set;
Seps : constant String := " ";
Utime : Long_Integer;
Stime : Long_Integer;
Sum_Time : Long_Integer;
begin
Open (F, In_File, File_Name);
String_Split.Create (S => Subs, -- Store sliced data in Subs
From => Get_Line (F), -- Read data to slice. We only need the first line of the stat file
Separators => Seps, -- Separator (here space)
Mode => String_Split.Multiple);
String_Split.Create (S => Subs, From => Get_Line (F), Separators => Seps, Mode => String_Split.Multiple);
Close (F);
Utime := Long_Integer'Value (String_Split.Slice (Subs, 14));
Stime := Long_Integer'Value (String_Split.Slice (Subs, 15));
Sum_Time := Utime + Stime;
return Sum_Time;

-- Reading cpu time from /proc/pid/stat
-- We only need utime and stime (user and system time)
-- utime is at index 13, stime at index 14 (assuming index starts at 0)
-- utime %lu : Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time spent running a virtual CPU, see below), so that applications that are not aware of the guest time field do not lose that time from their calculations.
-- stime %lu : Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
-- Example of line: 25152 (java) S 12564 1685 1685 0 -1 1077960704 155132 412 478 2 11617 1816 0 0 20 0 61 0 2001362 3813126144 99139 18446744073709551615 4194304 4196724 140736365379696 140736365362368 140056419567211 0 0 4096 16796879 18446744073709551615 0 0 17 2 0 0 3 0 0 6294960 6295616 13131776 140736365387745 140736365388341 140736365388341 140736365391821 0
-- fscanf(fp, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu %lu", &cpu_process_data->utime, &cpu_process_data->stime);
Utime := Long_Integer'Value (String_Split.Slice (Subs, 14)); -- Index 13 in file. Slice function starts index at 1, so it is 14
Stime := Long_Integer'Value (String_Split.Slice (Subs, 15)); -- Index 14 in file. Slice function starts index at 1, so it is 15
exception
when NAME_ERROR | STATUS_ERROR =>
Put_Line ("Error opening or reading the file: " & File_Name);
return 0;
when DATA_ERROR | NUMERIC_ERROR =>
Put_Line ("Error converting data from the file: " & File_Name);
return 0;
when others =>
Put_Line ("Unknown error processing the file: " & File_Name);
return 0;
end Get_TID_Time;



procedure Calculate_PID_Time (PID_Data : in out CPU_STAT_PID_Data; Is_Before : in Boolean) is
Task_Directory : constant String := "/proc/" & Trim(Integer'Image(PID_Data.PID_Number), Ada.Strings.Left) & "/task";
Command : constant String := "ls " & Task_Directory;
Args : Argument_List_Access;
Status : aliased Integer;
Subs : String_Split.Slice_Set; -- Used to slice the read data from stat file
Seps : constant String := String'(1 => Character'Val (10)); -- Newline for slicing string
Slice_number_count : String_Split.Slice_Number;
Loop_I : Integer;
TID_Number : Integer;
TID_Counter : Integer := 0;
TID_Total_Time : Long_Integer := 0;
type TID_Array_Int is array (1..100) of Integer;
TID_Array : TID_Array_Int; -- Array of all TIDs of the application


begin
Args := Argument_String_To_List (Command);
TID_Array := (others => -1);
declare
Response : String :=
Get_Command_Output
(Command => Args (Args'First).all,
Arguments => Args (Args'First + 1 .. Args'Last),
Input => "",
Status => Status'Access);
begin
Free (Args);
String_Split.Create (S => Subs, -- Store sliced data in Subs
From => Response, -- Read data to slice
Separators => Seps, -- Separator (here space)
Mode => String_Split.Multiple);
Slice_number_count := String_Split.Slice_Count (Subs);

for I in 1 .. Slice_number_count loop
Loop_I := Integer'Value (String_Split.Slice_Number'Image (I));
TID_Array(Loop_I) := Integer'Value (String_Split.Slice (Subs, I));
TID_Counter := TID_Counter + 1;
end loop;
end;

for I in 1 .. TID_Counter loop
if TID_Array(I) /= -1 then
TID_Number := TID_Array (I);
TID_Total_Time := TID_Total_Time + Get_TID_Time (PID_Data.PID_Number, TID_Number);
end if;
end loop;
if (Is_Before) then
PID_Data.Before_Time := Utime + Stime; -- Total time
PID_Data.Before_Time := TID_Total_Time;
else
PID_Data.After_Time := Utime + Stime; -- Total time
PID_Data.After_Time := TID_Total_Time;
PID_Data.Monitored_Time := PID_Data.After_Time - PID_Data.Before_Time;
end if;
exception
when NAME_ERROR | STATUS_ERROR =>
Put_Line ("Error dealing with files in /proc/" & Trim(Integer'Image(PID_Data.PID_Number), Ada.Strings.Left) & "/task directory");
OS_Exit (0);
when DATA_ERROR =>
Put_Line ("Error related to data formatting or I/O");
OS_Exit (0);
when E : NUMERIC_ERROR =>
Put_Line ("Arithmetic error encountered");
Put_Line (Exception_Message (E));
OS_Exit (0);
when others =>
Put_Line ("Error reading " & File_Name & " file");
Put_Line ("Unknown error processing /proc/" & Trim(Integer'Image(PID_Data.PID_Number), Ada.Strings.Left) & "/task directory");
OS_Exit (0);
end;
end Calculate_PID_Time;


end CPU_STAT_PID;


0 comments on commit a35af66

Please sign in to comment.