PDA

View Full Version : [Visual Basic] Ottenere la percentuale di carico della CPU


MaRgEnIuS
12-02-2010, 17:05
Salve a tutti! Sto realizzando un piccolo progetto per cui ho avuto la necessità di creare un'applicazione in Visual Basic per ottenere alcune informazioni dal processore. Quella principale in cui mi sono imbattuto consiste nell'estrapolazione del valore della percentuale di carico dell CPU... Mi sono documentato in giro, ed ho trovato alcune istruzioni interessanti inerenti le classi WMI. Sono riuscito a creare il codice ed a fare funzionare l'applicazione in diversi PC, testandola sia su Win XP che su Vista. Il problema è che alcuni PC non e vogliono proprio sapere di farla funzionare... All'avvio del programma al posto del valore del carico non mi viene visualizzato nulla... Da alcune fonti ho appurato che probabilmente alcune schede madri non supportano l'SMBIOS, e appunto per questo mi è stato consigliato di prelevare queste info direttamente dalle API di Windows o dal registro di sistema... Il problema è che essendoci poche informazioni in giro in merito a queste funzioni io non saprei bene come fare. Non è che per caso qualcuno potrebbe darmi qualche consiglio su come reperire questa piccola informazione utile per il mio progetto ? Grazie in ogni caso di aver letto... Spero in qualche risposta, grazie ed a presto!!!

;)

gugoXX
12-02-2010, 17:50
Sono informazioni che si possono prendere da ciascuno dei processi attivi sulla macchina, senza scomodare la WMI.
System.Diagnostic.Process.GetProcesses() per la lista dei processi.
Ciascun processo ha l'info TotalProcessorTime. Giocando con i ProcessorTime di tutti i processi puoi ricavare la percentuale di carico.

WarDuck
12-02-2010, 18:31
Se non vado errato la classe PerformanceCounter è quello che fa al caso tuo :D .

nuovoUtente86
12-02-2010, 20:05
riuscendo a ricavare la percentuale del processo in idle, trovi la controparte occupata.

MarcoGG
13-02-2010, 10:24
Sul web ci sono parecchi esempi, e tutti utilizzano più o meno questo :
PerformanceCounter("Processor", "% Processor Time", "_Total")
Un possibile utilizzo con un Timer e una ListBox potrebbe essere questo :
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick

Dim tempo As DateTime = DateTime.Now
Using PC As New PerformanceCounter("Processor", "% Processor Time", "_Total")
ListBox1.Items.Add(tempo & " -> " & CInt(PC.NextValue) & " %")
End Using

End Sub

Il problema è che ( almeno a me ) restituisce sempre 0%, anche mentre Task Manager dice tutt'altro...
Provate anche voi e fatemi sapere.

MaRgEnIuS
13-02-2010, 18:00
Allora provando ad utilizzare la proprietà TotalProcessorTime mi restituisce il messaggio "Accesso negato". Ecco il codice che ho usato:


Imports System
Imports System.Diagnostics.Process
Imports System.ComponentModel
Public Class Form1

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

Dim temp As Process()
temp = Process.GetProcesses()
Dim x As Integer
For x = 0 To temp.Length - 1
MessageBox.Show("Process: " & temp(x).TotalProcessorTime.Tostring)
Next

End Sub

End Class


Ovviamente il codice è molto grezzo, in quanto a me interessava che leggesse il valore..., in quel caso li butterei dentro ad una griglia...

Utilizzando invece la classe PerformanceCounter non mi restituisce nulla nella ListBox... Mbah..., non riesco proprio a capire come mai sia così difficile ottenere un'informazione così banale! Cmq grazie dell'aiuto, spero in qualche altro chiarimento da voi esperti!!!

;)

MarcoGG
14-02-2010, 10:13
Bastava che mettessi il codice esposto ad errori in un blocco Try Catch. Molto più semplicemente :
For Each P As Process In Process.GetProcesses
Try
ListBox1.Items.Add(P.ProcessName & " -> " & P.TotalProcessorTime.ToString)
Catch ex As Exception
ListBox1.Items.Add(P.ProcessName & " -> " & "Accesso Negato.")
End Try
Next
ListBox1 è appunto una ListBox su Form che presenta i valori per ogni processo.
Come si può notare "Accesso Negato" succede su vari processi che non permettono la lettura del valore TotalProcessorTime.
Nel mio caso ho un paio di "svchost", "Idle", e altri...

In ogni caso chiederei a chi ha consigliato questa tecnica di spiegare :
1. Come risolvere gli accessi negati.
2. Come ricavare la percentuale di utilizzo Cpu del singolo processo da un valore stringa come ad esempio questo :
svchost -> 00:00:00.0468750

gugoXX
14-02-2010, 10:31
Ciao. Il valore non e' una stringa, e' un TimeSpan.
Se si campiona il valore di totalprocesstime su tutti i processi ogni secondo, si sommano tutti insieme, e si sottrae questo valore da 1 secondo, si dovrebbe ottenere il tempo libero di CPU.

MaRgEnIuS
15-02-2010, 01:36
Allora, dopo diversi tentativi sono riuscito finalmente ad impostare la funzione per farmi stampare i valori, il problema è che credo non sia questa l'informazione che mi serve. Mandando il programma in esecuzione ottengo nella ListBox l'elenco dei nomi dei processi con accanto il rispettivo valore del tempo che impiega. Ora io mi sono creato una variabile sempre del tipo TimeSpan che si va incrementando del valore di ogni processo per ottenere il carico finale. Alla fine mi dà appunto un valore nel formato classico del TimeSpan del tipo (XX:XX:XX.XXXXXXX). Il mio problema è che riflettendo a fondo sul risultato ottenuto ho realizzato che si tratta di un tempo di esecuzione e non di una percentuale di carico, o sbaglio ? Il risultato che a me interesserebbe ottenere è il valore della percentuale di carico della cpu, ad esempio (10%, 20%, ecc.) per ogni processo, o meglio ancora se ottenessi la percentuale totale di tutti i processi! Ad ogni modo lascio trarre conclusione a chi ne sa di più..., nello stesso tempo ringrazio tutti quanti per gli aiuti ricevuti sino ad ora! Spero in ulteriori chiarimenti!!!

;)

gugoXX
15-02-2010, 09:59
Allora, dopo diversi tentativi sono riuscito finalmente ad impostare la funzione per farmi stampare i valori, il problema è che credo non sia questa l'informazione che mi serve. Mandando il programma in esecuzione ottengo nella ListBox l'elenco dei nomi dei processi con accanto il rispettivo valore del tempo che impiega. Ora io mi sono creato una variabile sempre del tipo TimeSpan che si va incrementando del valore di ogni processo per ottenere il carico finale. Alla fine mi dà appunto un valore nel formato classico del TimeSpan del tipo (XX:XX:XX.XXXXXXX). Il mio problema è che riflettendo a fondo sul risultato ottenuto ho realizzato che si tratta di un tempo di esecuzione e non di una percentuale di carico, o sbaglio ? Il risultato che a me interesserebbe ottenere è il valore della percentuale di carico della cpu, ad esempio (10%, 20%, ecc.) per ogni processo, o meglio ancora se ottenessi la percentuale totale di tutti i processi! Ad ogni modo lascio trarre conclusione a chi ne sa di più..., nello stesso tempo ringrazio tutti quanti per gli aiuti ricevuti sino ad ora! Spero in ulteriori chiarimenti!!!

;)

Allora, Sapendo quanto tempo e' passato da un certo momento, e quanto tempo ciascun processo ha trascorso facendo qualcosa nello stesso lasso di tempo, puoi calcolare le percentuali che ti servono, con un po' di matematica.

Oppure provi a seguire la strada del performancecounter, che penso abbia gia' le percentuali che ti servono.

MarcoGG
15-02-2010, 13:48
Allora, Sapendo quanto tempo e' passato da un certo momento, e quanto tempo ciascun processo ha trascorso facendo qualcosa nello stesso lasso di tempo, puoi calcolare le percentuali che ti servono, con un po' di matematica.


Mmm, un esempio ? :stordita:


Oppure provi a seguire la strada del performancecounter, che penso abbia gia' le percentuali che ti servono.


Il fatto è che PerformanceCounter a quanto pare non risolve, a chi ( a me ), restituisce sempre 0, a chi nulla... :(

MaRgEnIuS
16-02-2010, 02:18
Allora, stasera ho provato anche con la classe PerformanceCounter... Questo è il codice che ho utilizzato:


Imports System
Imports System.Windows.Forms
Imports System.Diagnostics
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim oPerf1 As New System.Diagnostics.PerformanceCounter
oPerf1.CategoryName = "Processor"
oPerf1.CounterName = "% Processor Time"
oPerf1.InstanceName = "0"
Dim I As Integer
For I = 0 To 100
ListBox1.Items.Add(oPerf1.NextValue().ToString() & "%")
Threading.Thread.Sleep(20)
ListBox1.Items.Add(oPerf1.NextValue().ToString() & "%")
Next
End Sub
End Class


Il risultato che ho ottenuto è una lista di valori, alcuni dei quali uguali fra loro. Ho fatto anche il test su altri computer senza il framework .NET aggiornato e l'applicazione genera un'eccezione e il programma non va in esecuzione. Ad ogni modo, a parte questo, c'è per caso qualcuno che mi possa dare delucidazioni sul significato dei valori che ottengo in output utilizzando questo codice ?

:confused:

MarcoGG
16-02-2010, 10:16
Il risultato che ho ottenuto è una lista di valori, alcuni dei quali uguali fra loro. Ho fatto anche il test su altri computer senza il framework .NET aggiornato e l'applicazione genera un'eccezione e il programma non va in esecuzione. Ad ogni modo, a parte questo, c'è per caso qualcuno che mi possa dare delucidazioni sul significato dei valori che ottengo in output utilizzando questo codice ?


1. Il fatto del framework non aggiornato è ovvio. Se hai compilato per FW3.5 non è mai consigliabile farlo girare su una macchina che ad esempio ha FW2.0.

2. I valori ottenuti dipendono anche da cosa si specifica in InstanceName. A quanto ne so io :
- Using PC As New PerformanceCounter("Processor", "% Processor Time", "_Total") -> rilevamento totale cpu.
- Using PC As New PerformanceCounter("Processor", "% Processor Time", "0") -> rilevamento del solo Core "0", in un sistema multi-core... e così via.

3. Non capisco perchè nel tuo codice fai 2 rilevamenti nel For, comunque il mio codice di test :
Using PC As New PerformanceCounter("Processor", "% Processor Time", "_Total")
For i As Integer = 1 To 100
ListBox1.Items.Add(PC.NextValue.ToString & " %")
Threading.Thread.Sleep(20)
Next
End Using
Restituisce una sfilza di "0 %", intervallati ogni tanto da qualche 25% o 50%, mentre Task Manager ovviamente dice tutt'altro, e va da 1% a 4%, durante l'esecuzione... :rolleyes:

MaRgEnIuS
16-02-2010, 23:53
Allora, grazie innanzitutto per la risposta! Dunque, io ho effettuato 2 rilevamenti nel FOR per vedere se cambiava qualcosa nei risultati di output con l'inserimento di una threading nel mezzo tra una stampa e l'altra. A questo punto sono proprio bloccato... Non capisco proprio come ottenere la percentuale di carico reale!!! E' strano poi come mai ci sia tutta questa differenza tra i valori ottenuti e quelli del task manager.... Questo mi fa pensare che non siano quelli che misurino realmente il carico..., anche se a dire il vero ho notato che sovraccaricando il processore prima di mandare in esecuzione il programma nel momento in cui dopo lo eseguo diversi processi segnalano il 100% di carico. In ogni caso spero che si riesca a fare maggiore chiarezza!!!

;)

MarcoGG
17-02-2010, 00:11
Allora, grazie innanzitutto per la risposta! Dunque, io ho effettuato 2 rilevamenti nel FOR per vedere se cambiava qualcosa nei risultati di output con l'inserimento di una threading nel mezzo tra una stampa e l'altra. A questo punto sono proprio bloccato... Non capisco proprio come ottenere la percentuale di carico reale!!! E' strano poi come mai ci sia tutta questa differenza tra i valori ottenuti e quelli del task manager.... Questo mi fa pensare che non siano quelli che misurino realmente il carico..., anche se a dire il vero ho notato che sovraccaricando il processore prima di mandare in esecuzione il programma nel momento in cui dopo lo eseguo diversi processi segnalano il 100% di carico. In ogni caso spero che si riesca a fare maggiore chiarezza!!!

;)

Vero. Succede anche a me : se elimino la Sleep() ottengo alcuni "100%".
Ho scritto in questo thread, più che per rispondere in modo risolutivo, come spesso amo fare, solo per dare una mano e infine accodarmi alla tua richiesta, dato che non ho mai avuto il problema di calcolare da .NET il carico della o delle Cpu. Ci ho provato solo ora, leggendo questa discussione, ed effettivamente agganciarsi ai valori del Task Manager, che trovo siano sempre verosimili e attendibili, pare sia più arduo del previsto.
In particolare c'è un sacco di gente che sul web consiglia quella tecnica col PerformanceCounter, ma evidentemente è un consiglio che rimbalza come un passa-parola e basta. Mi chiedo se ce ne sia uno che oltre a consigliare quella tecnica, l'abbia mai davvero provata. ( come spesso accade ) :D

MaRgEnIuS
22-02-2010, 04:16
Allora, in questi giorni ho creato un'applicazione con timer... utilizzando i vari totalprocesstime per vedere se riuscivo in qualche modo ad ottenere sta tanto desiderata percentuale. La percentuale, anche se probabilmente espressa in formato non corretto mi dà dei valori tra 99% e 100% di carico della CPU, anche se poi in realtà nel task manager i valori dicono tutt'altro. Io temo che questo sia causato dai processi la cui lettura viene negata. Infatti la variabile 'count' non tiene conto di questi. Qui sotto c'è il programma che ho creato. C'è qualcuno che per caso riesce a capire dove sta il problema ???


Imports System
Imports System.Management
Imports System.Windows.Forms
Imports System.Diagnostics
Public Class Form1
'Dichiarazioni variabili globali
Dim cpumhz As New Integer
Dim glbtimetot As Decimal

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Query per ottenere la frequenza della CPU
Try
Dim searcher As New ManagementObjectSearcher( _
"root\CIMV2", _
"SELECT * FROM Win32_Processor")
For Each queryObj As ManagementObject In searcher.Get()
'cpumhz contiene il valore della frequenza del processore in MHz
cpumhz = queryObj("MaxClockSpeed")
MessageBox.Show("Valore CPU in MHz: " & cpumhz)
TextBox1.Text = queryObj("MaxClockSpeed")
Next
Catch err As ManagementException
MessageBox.Show("An error occurred while querying for WMI data: " & err.Message)
End Try
Timer1.Enabled = True
End Sub

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
'count è la variabile accumulatore che contiene la somma di tutti i tempi dei processi
Dim count As TimeSpan
For Each P As Process In Process.GetProcesses
Try
'Incremento dell'accumulatore per formare il tempo totale in TimeSpan di tutti i processi
count = count + P.TotalProcessorTime
'In caso di errore in lettura esegue la Catch
Catch ex As Exception
ListBox1.Items.Add(P.ProcessName & " -> " & "Accesso Negato.")
End Try
Next
'Preleviamo dal timespan COUNT i valori di ore, minuti, secondi e millisecondi
Dim hh As Decimal = count.Hours
Dim mm As Decimal = count.Minutes
Dim ss As Decimal = count.Seconds
Dim mss As Decimal = count.Milliseconds
'Convertiamo ore, minuti e millisecondi in secondi
Dim hhs As Decimal = hh * (60 ^ 2)
Dim mms As Decimal = mm * 60
Dim msss As Decimal = mss * (10 ^ -3)
'Calcoliamo il tempo totale in secondi
Dim timetot As Decimal = hhs + mms + ss + msss
ListBox1.Items.Add(timetot)
'Tempo totale dei processi (convertito in secondi) - tempo totale dei processi precedente (N.B: da notare la variabile globale con cui si fa il confronto [glbtimetot])
Dim realbit As Decimal = timetot - glbtimetot
ListBox2.Items.Add(realbit)
'Frequenza CPU in MHz - differenza ottenuta precedentemente
Dim usedcpu As Decimal = cpumhz - realbit
'Calcolo delle percentuali di carico della CPU aggiornate
Dim percusedcpu As Decimal = usedcpu / (cpumhz * 100)
ListBox3.Items.Add(percusedcpu & "%")
'Assegnazione alla variabile globale 'glbtimetot' del valore del tempo totale in secondi di tutti i processi
glbtimetot = timetot
End Sub

End Class


Spero che qualcuno se ne faccia un'idea... Grazie!!!

;)

MaRgEnIuS
22-02-2010, 04:38
Allora, in questi giorni ho creato un'applicazione con timer... utilizzando i vari totalprocesstime per vedere se riuscivo in qualche modo ad ottenere sta tanto desiderata percentuale. La percentuale, anche se probabilmente espressa in formato non corretto mi dà dei valori tra 99% e 100% di carico della CPU, anche se poi in realtà nel task manager i valori dicono tutt'altro. Io temo che questo sia causato dai processi la cui lettura viene negata. Infatti la variabile 'count' non tiene conto di questi. Qui sotto c'è il programma che ho creato. C'è qualcuno che per caso riesce a capire dove sta il problema ???


Imports System
Imports System.Management
Imports System.Windows.Forms
Imports System.Diagnostics
Public Class Form1
'Dichiarazioni variabili globali
Dim cpumhz As New Integer
Dim glbtimetot As Decimal

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Query per ottenere la frequenza della CPU
Try
Dim searcher As New ManagementObjectSearcher( _
"root\CIMV2", _
"SELECT * FROM Win32_Processor")
For Each queryObj As ManagementObject In searcher.Get()
'cpumhz contiene il valore della frequenza del processore in MHz
cpumhz = queryObj("MaxClockSpeed")
MessageBox.Show("Valore CPU in MHz: " & cpumhz)
TextBox1.Text = queryObj("MaxClockSpeed")
Next
Catch err As ManagementException
MessageBox.Show("An error occurred while querying for WMI data: " & err.Message)
End Try
Timer1.Enabled = True
End Sub

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
'count è la variabile accumulatore che contiene la somma di tutti i tempi dei processi
Dim count As TimeSpan
For Each P As Process In Process.GetProcesses
Try
'Incremento dell'accumulatore per formare il tempo totale in TimeSpan di tutti i processi
count = count + P.TotalProcessorTime
'In caso di errore in lettura esegue la Catch
Catch ex As Exception
ListBox1.Items.Add(P.ProcessName & " -> " & "Accesso Negato.")
End Try
Next
'Preleviamo dal timespan COUNT i valori di ore, minuti, secondi e millisecondi
Dim hh As Decimal = count.Hours
Dim mm As Decimal = count.Minutes
Dim ss As Decimal = count.Seconds
Dim mss As Decimal = count.Milliseconds
'Convertiamo ore, minuti e millisecondi in secondi
Dim hhs As Decimal = hh * (60 ^ 2)
Dim mms As Decimal = mm * 60
Dim msss As Decimal = mss * (10 ^ -3)
'Calcoliamo il tempo totale in secondi
Dim timetot As Decimal = hhs + mms + ss + msss
ListBox1.Items.Add(timetot)
'Tempo totale dei processi (convertito in secondi) - tempo totale dei processi precedente (N.B: da notare la variabile globale con cui si fa il confronto [glbtimetot])
Dim realbit As Decimal = timetot - glbtimetot
ListBox2.Items.Add(realbit)
'Frequenza CPU in MHz - differenza ottenuta precedentemente
Dim usedcpu As Decimal = cpumhz - realbit
'Calcolo delle percentuali di carico della CPU aggiornate
Dim percusedcpu As Decimal = usedcpu / (cpumhz * 100)
ListBox3.Items.Add(percusedcpu & "%")
'Assegnazione alla variabile globale 'glbtimetot' del valore del tempo totale in secondi di tutti i processi
glbtimetot = timetot
End Sub

End Class


Spero che qualcuno se ne faccia un'idea... Grazie!!!

;)

MaRgEnIuS
23-02-2010, 17:34
Nuovamente salve!!! Allora, credo di aver abbandonato definitivamente l'idea di ottenere la percentuale di carico della CPU utilizzando il valore totalprocesstime, in quanto dopo diverse ore di lavoro sono riuscito a correggere gli errori che avevo in precedenza ma continuavo ad avere percentuali non corrette. Navigando in rete ho trovato però del codice interessante che spiega come ottenere questa informazione dalle API di Windows... Da quello che ho visto è la fonte più attendibile dalla quale si possono reperire tutti i valori utili per calcolare il carico... Io in quanto poco esperto con visual basic 2008 ho creato un progetto nel quale ho inserito il codice che ho trovato. L'unico problema è che io non ho ancora capito come farlo funzionare. Posto qui sotto il codice che ho trovato... C'è per caso qualcuno che possa darmi una dritta su come rendere funzionante questo codice ?


Public Class Form1

'This API Call "Requires" Windows XP with Service Pack 1 or Higher.

Public Declare Auto Function GetSystemTimes Lib "kernel32.dll" (ByRef idleTime As Int64, ByRef kernelTime As Int64, ByRef userTime As Int64) As Boolean
'This variable is used to specify how often to check and display the CPU Usage value.

Dim updateSpeed As Integer = 250

'These are to hold the starting times values.

Dim sIdle As Long

Dim sKernel As Long

Dim sUser As Long
'These variable are to hold the ending times values.

Dim eIdle As Long

Dim eKernel As Long

Dim eUser As Long

'

'These will hold the values that are calculated from the ending and starting times values.

Dim cIdle As Integer

Dim cKernel As Integer

Dim cUser As Integer



'Once thats taken care I created 2x more variables for calculating purposes...



'

'This variable will contain the values of cKernel + cUser.

Dim systemTime As Integer

'

'Will contain the calculated cpu usage as a percent value.

Dim totalCpuUsage As Double

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

End Sub

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
'Retrieve the Times values before starting the delay.

GetSystemTimes(sIdle, sKernel, sUser)

sIdle = Val(sIdle)

sKernel = Val(sKernel)

sUser = Val(sUser)

'

'The delay for how often to check and update the cpu usage values.

'Note: You can use the sleep api if you want or the simple pause sub I added.

Threading.Thread.Sleep(updateSpeed)

'

'Retrieve the Times values after the Delay.

GetSystemTimes(eIdle, eKernel, eUser)

'

eIdle = Val(eIdle)

eKernel = Val(eKernel)

eUser = Val(eUser)

'

'Get the values calculated between the starting and ending Times values.

cIdle = (eIdle - sIdle)

cKernel = (eKernel - sKernel)

cUser = (eUser - sUser)

'

'Calculate the total system time. Which is Kernel and User time together.

systemTime = (cKernel + cUser)

Application.DoEvents()

'

'Now calculate the values as a percentage based number.

totalCpuUsage = (systemTime - cIdle) * (100) / (systemTime)

'Simply display the calulated results in a label control.

ListBox1.Items.Add("Total CPU Usage: " & totalCpuUsage & "%")



'CPU Usage Example using GetSystemTimes()
End Sub

Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged

End Sub
End Class


Spero di ottenere qualche chiarimento... Non pretendo di avere la pappa pronta..., ma mi piacerebbe capire come far funzionare l'applicazione!!! :help: Grazie!!!

;)