Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add handling of multi-threading by PID #36

Merged
merged 1 commit into from
Jun 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;