powerjoular_0.7.0_92d4a0eb/src/powerjoular.adb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
--
--  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
--  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 : Adel Noureddine
--

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with GNAT.Command_Line; use GNAT.Command_Line;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with GNAT.OS_Lib; use GNAT.OS_Lib;
with GNAT.Ctrl_C; use GNAT.Ctrl_C;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with Ada.Characters.Latin_1; use Ada.Characters.Latin_1;
with Ada.Command_Line; use Ada.Command_Line;

with CPU_Cycles; use CPU_Cycles;
with CSV_Power; use CSV_Power;
with Help_Info; use Help_Info;
with CPU_STAT_PID; use CPU_STAT_PID;
with Intel_RAPL_sysfs; use Intel_RAPL_sysfs;
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;

procedure Powerjoular is
    -- Power variables
    --
    -- CPU Power
    CPU_Power : Float; -- Entire CPU power consumption
    Previous_CPU_Power : Float := 0.0; -- Previous CPU power consumption (t - 1)
    PID_CPU_Power : Float; -- CPU power consumption of monitored PID
    App_CPU_Power : Float; -- CPU power consumption of monitored application
    CPU_Energy : Float := 0.0;
    --
    -- GPU Power
    GPU_Power : Float := 0.0;
    Previous_GPU_Power : Float := 0.0; -- Previous GPU power consumption (t - 1)
    GPU_Energy : Float := 0.0;
    --
    -- Total Power and Energy
    Previous_Total_Power : Float := 0.0; -- Previous entire total power consumption (t - 1)
    Total_Power : Float := 0.0; -- Total power consumption of all hardware components
    Total_Energy : Float := 0.0; -- Total energy consumed since start of PowerJoular until exit

    -- Data types for Intel RAPL energy monitoring
    RAPL_Before : Intel_RAPL_Data; -- Intel RAPL data
    RAPL_After : Intel_RAPL_Data; -- Intel RAPL data
    RAPL_Energy : Float; -- Intel RAPL energy difference for monitoring cycle

    -- Data types for Nvidia energy monitoring
    Nvidia_Supported : Boolean; -- If nvidia card, drivers and smi tool are available

    -- Raspberrry Pi model settings
    Algorithm_Name : Unbounded_String := To_Unbounded_String ("polynomial"); -- Regression model type (by default, polynomial regression model)

    -- Data types to monitor CPU cycles
    CPU_CCI_Before : CPU_Cycles_Data; -- Entire CPU cycles
    CPU_CCI_After : CPU_Cycles_Data; -- Entire CPU cycles
    CPU_PID_Monitor : CPU_STAT_PID_Data; -- Monitored PID CPU cycles and power
    CPU_App_Monitor : CPU_STAT_App_Data; -- Monitored App CPU cycles and power

    -- CPU utilization variables
    CPU_Utilization : Float; -- Entire CPU utilization
    PID_CPU_Utilization : Float; -- CPU utilization of monitored PID
    App_CPU_Utilization : Float; -- CPU utilization of monitored application

     -- OS name
    OS_Name : String := Get_OS_Name;

    -- 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

    -- Settings
    Show_Terminal : Boolean := False; -- Show power data on terminal
    Show_Debug : Boolean := False; -- Show debug info on terminal
    Print_File: Boolean := False; -- Save power data in file
    Monitor_PID : Boolean := False; -- Monitor a specific PID
    Monitor_App : Boolean := False; -- Monitor a specific application by its name
    Overwrite_Data : Boolean := false; -- Overwrite data instead of append on file

    -- Procedure to capture Ctrl+C to show total energy on exit
    procedure CtrlCHandler is
    begin
        New_Line;
        Put_Line ("--------------------------");
        Put ("Total energy: ");
        Put (Total_Energy, Exp => 0, Fore => 0, Aft => 2);
        Put_Line (" Joules, including:");
        Put (HT & "CPU energy: ");
        Put (CPU_Energy, Exp => 0, Fore => 0, Aft => 2);
        Put_Line (" Joules");
        Put (HT & "GPU energy: ");
        Put (GPU_Energy, Exp => 0, Fore => 0, Aft => 2);
        Put_Line (" Joules");
        Put_Line ("--------------------------");
        OS_Exit (0);
    end CtrlCHandler;

begin
    -- Capture Ctrl+C and redirect to handler
    Install_Handler(Handler => CtrlCHandler'Unrestricted_Access);

    -- Default CSV filename
    CSV_Filename := To_Unbounded_String ("./powerjoular-power.csv");

    -- 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;
    end loop;

    if (Argument_Count = 0) then
        Show_Terminal := True;
    end if;

    -- If platform not supported, then exit program
    if (Platform_Name = "") then
        Put_Line ("Platform not supported");
        Put_Line (OS_Name);
        return;
    end if;

    if Show_Debug then
        Put_Line ("System info:");
        Put_Line (Ada.Characters.Latin_1.HT & "Platform: " & Platform_Name);
    end if;

    if Check_Intel_Supported_System (Platform_Name) then
        -- For Intel RAPL, check and populate supported packages first
        Check_Supported_Packages (RAPL_Before, "psys");
        if RAPL_Before.psys_supported and Show_Debug then
            Put_Line (Ada.Characters.Latin_1.HT & "Intel RAPL psys: " & Boolean'Image (RAPL_Before.Psys_Supported));
        end if;

        if (not RAPL_Before.psys_supported) then -- Only check for pkg and dram if psys is not supported
            Check_Supported_Packages (RAPL_Before, "pkg");
            Check_Supported_Packages (RAPL_Before, "dram");
            if RAPL_Before.Pkg_Supported and Show_Debug then
                Put_Line (Ada.Characters.Latin_1.HT & "Intel RAPL pkg: " & Boolean'Image (RAPL_Before.pkg_supported));
            end if;
            if RAPL_Before.Dram_Supported and Show_Debug then
                Put_Line (Ada.Characters.Latin_1.HT & "Intel RAPL dram: " & Boolean'Image (RAPL_Before.Dram_Supported));
            end if;
        end if;
        RAPL_After := RAPL_Before; -- Populate the "after" data type with same checking as the "before" (insteaf of wasting redundant calls to procedure)

        -- Check if Nvidia card is supported
        -- For now, Nvidia support requiers a PC/server, thus Intel support
        Nvidia_Supported := Check_Nvidia_Supported_System;
        if Nvidia_Supported and Show_Debug then
            Put_Line (Ada.Characters.Latin_1.HT & "Nvidia supported: " & Boolean'Image (Nvidia_Supported));
        end if;
    end if;

    -- Amend PID CSV file with PID number
    if Monitor_PID then
        PID_Or_App_CSV_Filename := CSV_Filename & "-" & Trim(Integer'Image (CPU_PID_Monitor.PID_Number), Ada.Strings.Left) & ".csv";
        if Show_Debug then
            Put_Line ("Monitoring PID: " & Integer'Image (CPU_PID_Monitor.PID_Number));
        end if;
    end if;

    -- Amend App CSV file with App name
    if Monitor_App then
        PID_Or_App_CSV_Filename := CSV_Filename & "-" & CPU_App_Monitor.App_Name & ".csv";
        if Show_Debug then
            Put_Line ("Monitoring application: " & To_String (CPU_App_Monitor.App_Name));
        end if;
    end if;

    -- Main monitoring loop
    loop
        -- Get a first snapshot of current entire CPU cycles
        Calculate_CPU_Cycles (CPU_CCI_Before);
        if Monitor_PID then -- Do the same for CPU cycles of the monitored PID
            Calculate_PID_Time (CPU_PID_Monitor, True);
        end if;

        if Monitor_App then -- Do the same for CPU cycles of the monitored application
            -- First update the PID array for the application
            -- We do it every cycle so PID list is always current and accurate
            Update_PID_Array (CPU_App_Monitor);
            Calculate_App_Time (CPU_App_Monitor, True);
        end if;

        if Check_Intel_Supported_System (Platform_Name) then
            -- Get a first snapshot of Intel RAPL energy data
            Calculate_Energy (RAPL_Before);
        end if;

        -- Wait for 1 second
        delay 1.0;

        -- Get a second snapshot of current entire CPU cycles
        Calculate_CPU_Cycles (CPU_CCI_After);
        if Monitor_PID then -- Do the same for CPU cycles of the monitored PID
            Calculate_PID_Time (CPU_PID_Monitor, False);
        end if;

        if Monitor_App then -- Do the same for CPU cycles of the monitored application
            Calculate_App_Time (CPU_App_Monitor, False);
        end if;

        if Check_Intel_Supported_System (Platform_Name) then
            -- Get a first snapshot of Intel RAPL energy data
            Calculate_Energy (RAPL_After);
        end if;

        -- Calculate entire CPU utilization
        CPU_Utilization := (Float (CPU_CCI_After.cbusy) - Float (CPU_CCI_Before.cbusy)) / (Float (CPU_CCI_After.ctotal) - 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;
            CPU_Power := RAPL_Energy;
            Total_Power := CPU_Power;
        end if;

        if Nvidia_Supported then
            -- Calculate GPU power consumption
            GPU_Power := Get_Nvidia_SMI_Power;
            -- Add GPU power to total power
            -- The total power displayed by PowerJoular is therefore : CPU + GPU power
            Total_Power := Total_Power + GPU_Power;
        end if;

        -- If a particular PID is monitored, calculate its CPU time, CPU utilization and CPU power
        if Monitor_PID then
            PID_CPU_Utilization := (Float (CPU_PID_Monitor.Monitored_Time)) / (Float (CPU_CCI_After.ctotal) - Float (CPU_CCI_Before.ctotal));
            PID_CPU_Power := (PID_CPU_Utilization * CPU_Power) / CPU_Utilization;

            -- Show CPU power data on terminal of monitored PID
            if Show_Terminal then
                Show_On_Terminal_PID (PID_CPU_Utilization, PID_CPU_Power, CPU_Utilization, CPU_Power, True);
            end if;

            -- Save CPU power data to CSV file of monitored PID
            if Print_File then
                Save_PID_To_CSV_File (To_String (PID_Or_App_CSV_Filename), PID_CPU_Utilization, PID_CPU_Power, Overwrite_Data);
            end if;
        end if;

        -- If a particular application is monitored, calculate its CPU time, CPU utilization and CPU power
        if Monitor_App then
            -- PID_Time := CPU_PID_After.total_time - CPU_PID_Before.total_time;
            App_CPU_Utilization := (Float (CPU_App_Monitor.Monitored_Time)) / (Float (CPU_CCI_After.ctotal) - Float (CPU_CCI_Before.ctotal));
            App_CPU_Power := (App_CPU_Utilization * CPU_Power) / CPU_Utilization;

            -- Show CPU power data on terminal of monitored PID
            if Show_Terminal then
                Show_On_Terminal_PID (App_CPU_Utilization, App_CPU_Power, CPU_Utilization, CPU_Power, False);
            end if;

            -- Save CPU power data to CSV file of monitored PID
            if Print_File then
                Save_PID_To_CSV_File (To_String (PID_Or_App_CSV_Filename), App_CPU_Utilization, App_CPU_Power, Overwrite_Data);
            end if;
        end if;

        -- Show total power data on terminal
        if Show_Terminal and then (not Monitor_PID) and then (not Monitor_App) then
            Show_On_Terminal (CPU_Utilization, Total_Power, Previous_Total_Power, CPU_Power, GPU_Power, Nvidia_Supported);
        end if;

        Previous_CPU_Power := CPU_Power;
        Previous_GPU_Power := GPU_Power;
        Previous_Total_Power := Total_Power;

        -- Increment total energy with power of current cycle
        -- Cycle is 1 second, so energy for 1 sec = power
        Total_Energy := Total_Energy + Total_Power;
        CPU_Energy := CPU_Energy + CPU_Power;
        GPU_Energy := GPU_Energy + GPU_Power;

        -- Save total power data to CSV file
        if Print_File then
            Save_To_CSV_File (To_String (CSV_Filename), CPU_Utilization, Total_Power, CPU_Power, GPU_Power, Overwrite_Data);
        end if;
    end loop;
end Powerjoular;