Avatar billede mtj111 Novice
04. august 2009 - 17:57 Der er 13 kommentarer og
1 løsning

Performance Counters

Hej

Er der nogle der kan hjælpe mig med at tilgå Windows' Performance Counters?

F.eks. til at aflæse CPU-forbrug per kerne, ram-forbrug eller andet.

Det eneste kodeeksempel jeg kan finde er http://w-shadow.com/blog/2009/04/17/per-core-cpu-usage/
Men jeg kan desværre ikke få den til at virke (Der sker nogle fejl i Jedi API Library).

Med venlig hilsen
Michael :)
Avatar billede Slettet bruger
04. august 2009 - 18:02 #1
Hvilke fejl?
Avatar billede MasterException Nybegynder
04. august 2009 - 22:43 #2
jeg ved i system.diagnostics namespacet er det et hav af operationer til dette. det skulle være underligt, hvis der ikke var noget du kunne bruge der. jeg har selv lavet en Performance Counters og det var udelukkede ud fra system.diagnostics
Avatar billede MasterException Nybegynder
04. august 2009 - 22:47 #3
men fejl troede det var et c# spg. undskylder :-)
Avatar billede mtj111 Novice
05. august 2009 - 19:57 #4
jape44: Den melder fejl i jwawindows.pas:
[Fatal Error] JwaWindows.pas(213): F1026 File not found: 'msxml.dcu'
Jeg har prøvet at google filen, men uden held.
Er der nogle af jer, der har den? :)

MasterException: Helt i orden :)
Avatar billede mbsnet Nybegynder
06. august 2009 - 01:55 #5
CPU pr kerne kan aflæses i WinNT via denne unit:
(fandt den et sted på nettet via google)

unit cpuCore;

interface

uses
windows;

//WinNT only in this edition
//The last (fake) CPU usage counter represents the avarage CPU load.

procedure collectCpuData;
function getCpuUsage(const id:integer):double;
function getCpuCount:integer;

implementation

type
pInt64=^int64;

tPerfDataBlock=packed record signature:array[0..3] of wChar;
  littleEndian,vs,rev,totalbyteLn,headerLn,numObjectTypes:dWord;
  defaultObject:longInt;sysTime:tSystemTime;reserved:dWord;
  perfTime,perfFreq,perfTime100nSec:int64;sysNameLn,systemNameOffset:dWord
end;pPerfDataBlock=^tPerfDataBlock;

tPerfObjType=packed record
  totalbyteLn,defLn,headerLn,objNti:dWord;
  objectNameTitle:lpwStr;objHti:dWord;objHt:lpwStr;
  detailLevel,numCounters:dWord;defCounter,numInstances:longInt;
  codePage:dWord;perfTime,perfFreq:int64
end;pPerfObjType=^tPerfObjType;

tPerfCounterDef=packed record byteLn,counterNti:dWord;
  counterTit:lpwStr;counterHti:dWord;counterHt:lpwStr;
  defaultScale:longInt;detailLevel,counterType,counterSize,counterOffset:dWord
end;pPerfCounterDef=^tPerfCounterDef;

tPerfCounterBlock=packed record byteLn:dWord end;
pPerfCounterBlock=^tPerfCounterBlock;

tPerfInstanceDef=packed record byteLn,parentObjectTitleIndex,
  parentObjectInstance:dWord;uniqueId:longInt;nameOffset,nameLength:dWord
end;pPerfInstanceDef=^tPerfInstanceDef;

const
cpuUsageIdx      = 6;
processor_idx    = 238;
processor_idx_str = '238';

type
aInt64=array[0..$FFFF] of int64;
pAInt64=^Aint64;

var
fPerfData                      :pPerfDataBlock;
fBufSize,fCpuCount              :integer;
pot                            :pPerfObjType;
pcd                            :pPerfCounterDef;
fCounters,fPrevCounters        :pAint64;
fSysTime,fPrevSysTime          :int64;

procedure collectCpuData;
var i,bs:integer;st:tFileTime;
    pcbI:pPerfCounterBlock;pidI:pPerfInstanceDef;
begin

BS:=fBufSize;

while regQueryValueEx(HKEY_PERFORMANCE_DATA,processor_IDX_Str,nil,nil,pByte(fPerfData),@bs)=ERROR_MORE_DATA do begin
  inc(fBufSize,$1000);bs:=fBufSize;reallocMem(fPerfData,fBufSize)
end;

pot:=pPerfObjType(dWord(fPerfData)+fPerfData.headerLn);
for i:=1 to fPerfData.NumObjectTypes do begin
  if pot.objNti=processor_IDX then break;
  pot:=pPerfObjType(dWord(pot)+pot.TotalbyteLn);
end;

if pot.objNti<>Processor_IDX then exit;//raise Exception.Create('Unable to locate the "Processor" performance object');

if fCpuCount<0 then begin fCpuCount:=pot.numInstances;
  getMem(fCounters,fCpuCount*sizeOf(int64));getMem(fPrevCounters,fCpuCount*sizeOf(int64));
end;

pcd:=pPerfCounterDef(dWord(pot)+pot.headerLn);
for i:=1 to pot.numCounters do begin
  if pcd.counterNti=cpuUsageIdx then break;
  pcd:=pPerfCounterDef(dWord(pcd)+pcd.byteLn);
end;

if pcd.counterNti<>cpuUsageIdx then exit;//raise Exception.Create('Unable to locate the "% of CPU usage" performance counter');

pidI:=pPerfInstanceDef(dWord(pot)+pot.defLn);
for i:=0 to fCpuCount-1 do begin
  pcbI:=pPerfCounterBlock(dWord(pidI)+pidI.byteLn);
  fPrevCounters[i]:=fCounters[i];
  fCounters[i]:=int64(pInt64(dWord(pcbI)+pcd.CounterOffset)^);
  pidI:=pPerfInstanceDef(dWord(pcbI)+pcbI.byteLn);
end;

fPrevSysTime:=fSysTime;systemTimeToFileTime(fPerfData.sysTime,st);fSysTime:=int64(st)
end;

function getCPUCount:integer;
begin
if fCpuCount<0 then collectCPUData;
result:=fCpuCount
end;

function getCpuUsage(const id:integer):double;
begin result:=0;
if fCpuCount<0 then collectCpuData;
if (id<0) or (id>fCpuCount-1) then exit;
if fPrevSysTime=fSysTime then result:=0 else
result:=1-(fCounters[id]-fPrevCounters[id])/(fSysTime-fPrevSysTime)
end;

initialization
fCpuCount:=-1;
fBufSize:=$2000;
getMem(fPerfData,fBufSize);fillChar(fPerfData^,fBufSize,0)

finalization
freeMem(fPerfData)

end.


unit mbsCpuUsage;//WinNT edition

interface

uses
windows;

//The last (fake) CPU usage counter represents the avarage CPU load.

procedure collectCpuData;
function getCpuUsage(const id:integer):double;
function getCpuCount:integer;

implementation

type
pInt64=^int64;

tPerfDataBlock=packed record signature:array[0..3] of wChar;
  littleEndian,vs,rev,totalbyteLn,headerLn,numObjectTypes:dWord;
  defaultObject:longInt;sysTime:tSystemTime;reserved:dWord;
  perfTime,perfFreq,perfTime100nSec:int64;sysNameLn,systemNameOffset:dWord
end;pPerfDataBlock=^tPerfDataBlock;

tPerfObjType=packed record
  totalbyteLn,defLn,headerLn,objNti:dWord;
  objectNameTitle:lpwStr;objHti:dWord;objHt:lpwStr;
  detailLevel,numCounters:dWord;defCounter,numInstances:longInt;
  codePage:dWord;perfTime,perfFreq:int64
end;pPerfObjType=^tPerfObjType;

tPerfCounterDef=packed record byteLn,counterNti:dWord;
  counterTit:lpwStr;counterHti:dWord;counterHt:lpwStr;
  defaultScale:longInt;detailLevel,counterType,counterSize,counterOffset:dWord
end;pPerfCounterDef=^tPerfCounterDef;

tPerfCounterBlock=packed record byteLn:dWord end;
pPerfCounterBlock=^tPerfCounterBlock;

tPerfInstanceDef=packed record byteLn,parentObjectTitleIndex,
  parentObjectInstance:dWord;uniqueId:longInt;nameOffset,nameLength:dWord
end;pPerfInstanceDef=^tPerfInstanceDef;

const
cpuUsageIdx      = 6;
processor_idx    = 238;
processor_idx_str = '238';

type
aInt64=array[0..$FFFF] of int64;
pAInt64=^Aint64;

var
fPerfData                      :pPerfDataBlock;
fBufSize,fCpuCount              :integer;
pot                            :pPerfObjType;
pcd                            :pPerfCounterDef;
fCounters,fPrevCounters        :pAint64;
fSysTime,fPrevSysTime          :int64;

procedure collectCpuData;
var i,bs:integer;st:tFileTime;
    pcbI:pPerfCounterBlock;pidI:pPerfInstanceDef;
begin

BS:=fBufSize;

while regQueryValueEx(HKEY_PERFORMANCE_DATA,processor_IDX_Str,nil,nil,pByte(fPerfData),@bs)=ERROR_MORE_DATA do begin
  inc(fBufSize,$1000);bs:=fBufSize;reallocMem(fPerfData,fBufSize)
end;

pot:=pPerfObjType(dWord(fPerfData)+fPerfData.headerLn);
for i:=1 to fPerfData.NumObjectTypes do begin
  if pot.objNti=processor_IDX then break;
  pot:=pPerfObjType(dWord(pot)+pot.TotalbyteLn);
end;

if pot.objNti<>Processor_IDX then exit;//raise Exception.Create('Unable to locate the "Processor" performance object');

if fCpuCount<0 then begin fCpuCount:=pot.numInstances;
  getMem(fCounters,fCpuCount*sizeOf(int64));getMem(fPrevCounters,fCpuCount*sizeOf(int64));
end;

pcd:=pPerfCounterDef(dWord(pot)+pot.headerLn);
for i:=1 to pot.numCounters do begin
  if pcd.counterNti=cpuUsageIdx then break;
  pcd:=pPerfCounterDef(dWord(pcd)+pcd.byteLn);
end;

if pcd.counterNti<>cpuUsageIdx then exit;//raise Exception.Create('Unable to locate the "% of CPU usage" performance counter');

pidI:=pPerfInstanceDef(dWord(pot)+pot.defLn);
for i:=0 to fCpuCount-1 do begin
  pcbI:=pPerfCounterBlock(dWord(pidI)+pidI.byteLn);
  fPrevCounters[i]:=fCounters[i];
  fCounters[i]:=int64(pInt64(dWord(pcbI)+pcd.CounterOffset)^);
  pidI:=pPerfInstanceDef(dWord(pcbI)+pcbI.byteLn);
end;

fPrevSysTime:=fSysTime;systemTimeToFileTime(fPerfData.sysTime,st);fSysTime:=int64(st)
end;

function getCPUCount:integer;
begin
if fCpuCount<0 then collectCPUData;
result:=fCpuCount
end;

function getCpuUsage(const id:integer):double;
begin result:=0;
if fCpuCount<0 then collectCpuData;
if (id<0) or (id>fCpuCount-1) then exit;
if fPrevSysTime=fSysTime then result:=0 else
result:=1-(fCounters[id]-fPrevCounters[id])/(fSysTime-fPrevSysTime)
end;

initialization
fCpuCount:=-1;
fBufSize:=$2000;
getMem(fPerfData,fBufSize);fillChar(fPerfData^,fBufSize,0)

finalization
freeMem(fPerfData)

end.
Avatar billede mbsnet Nybegynder
06. august 2009 - 01:57 #6
bemærk den er anført 2 gange i træk, sorry.
Avatar billede mtj111 Novice
08. august 2009 - 15:03 #7
Hej

mbsnet: Tak for dit forslag. Din kode tilgår Performance Counters via en "falsk" registreringsdatabase (HKEY_PERFORMANCE_DATA), som er skjult i regedit.exe.

Jeg ville dog helst hvis jeg kunne tilgå performance counters via noget "kode-halløj", og helst ikke via registreringsdatabasen.

Jeg har dog prøvet dit eksempel, og den giver desværre nogle ret mærkelige værdier - 0.99995, 1.000044 og 1.767024 for mine 3 processorer (jeg har godt nok kun 2 kerner - den siger jeg har 3).


Men hvis en kunne sende mig msxml.dcu (email er i min profil), så ville det være superdejligt :)
Avatar billede mbsnet Nybegynder
08. august 2009 - 20:50 #8
Jeg igen, jeg bruger selv den kode hver dag i min egen toolbar program, og den virker fint. den sidste cpu er en gennemsnit forbrugsmåler...
Avatar billede mbsnet Nybegynder
08. august 2009 - 21:12 #9
//The last (fake) CPU usage counter represents the avarage CPU load.
Avatar billede mbsnet Nybegynder
09. august 2009 - 02:42 #10
mtj111> resultatet ganges med 100, for at få antal procent
- eksempel:

//...
var
id:integer;
aPct:double;
begin
id:=0;
aPct:=getCpuUsage(id)*100;
caption:=floatToStr(aPct)+' %';
end;
Avatar billede mtj111 Novice
09. august 2009 - 11:10 #11
ah :) det forklarer den tredje processor

Hver gang jeg bruger GetCpuUsage får jeg 99,99996 ved id:=0 og 100,04 id:=1
Værdierne ser ikke ud til at skifte :/

Desuden vil den heller ikke opdatere værdien hvis jeg trykker på knappen igen (jeg har sat en knap til udføre GetCpuUsage og skrive det i en memo). Den giver blot de samme tal
Avatar billede mbsnet Nybegynder
10. august 2009 - 04:59 #12
hej igen.

Det er lidt omstændigt af aflæse dataene rigtigt.
Først køres collectCpuData,- så skal alle kerner aflæses, før collectCpuData må køres igen.

For at gøre det hele lidt nemmere, har jeg nu lavet et objekt (class), til at
styre dataene (med tråd osv.)

Så er det muligt af aflæse på kryds og tværs.

Ny opdateret unit, samt et eksempel på aflæsning:


unit mbsCpuUsage;
//CPU USAGE unit for Windows NT
//Modified: 2009-08-10, mortenbs.com
//Tested using Delphi 7 and Windows XP

interface

uses
windows;

const
MAX_CPU_COUNT                =  64;          //Max cpu count (array)
THRD_TIMER_DELAY              = 500;          //Thread update time (ms)

type
//-----------------------------|----------------|----------------------|----------------------------
str                          = ansiString;
lInt                          = longInt;
pInt64                        = ^int64;
aInt64                        = array[0..$FFFF] of int64;
pAInt64                      = ^aInt64;
tCpuFloats                    = array[0..MAX_CPU_COUNT-1] of extended;
tSimpleEvent                  = procedure of object;
//-----------------------------|----------------|----------------------|----------------------------
tCpuUsageNT=class(tObject)
  procedure reset;virtual;                      //Reset all states to zero
  function usagePct(const aCpuId:lInt):extended;//Get cpu core usage in percents
  function text(const aSep:str;const aAlsoWantAvrg:boolean=false):str;
  procedure notifyChanges;
private
  fOnChanges                  :tSimpleEvent;
protected
public
  data                        :tCpuFloats;    //Cpu core array in percents (extended)
  thrd                        :pointer;        //Updater thread pointer
  count                        :lInt;          //Cpu count
  constructor create;virtual;
  destructor destroy;override;
  property onChanges          :tSimpleEvent    read fOnChanges        write fOnChanges;
end;
//-----------------------------|----------------|----------------------|----------------------------

implementation

uses
sysUtils,classes;

type
//-----------------------------|----------------|----------------------|----------------------------
pPerfDataBlock=^tPerfDataBlock;
tPerfDataBlock=packed record
  signature                    :array[0..3] of wChar;
  littleEndian,vs,rev,
  totalbyteLn,headerLn,
  numObjectTypes              :dWord;
  defaultObject                :lInt;
  sysTime                      :tSystemTime;
  reserved                    :dWord;
  perfTime,perfFreq,
  perfTime100nSec              :int64;
  sysNameLn,systemNameOffset  :dWord
end;
//-----------------------------|----------------|----------------------|----------------------------
pPerfObjType=^tPerfObjType;
tPerfObjType=packed record
  totalbyteLn,defLn,
  headerLn,objNti              :dWord;
  objectNameTitle              :lpwStr;
  objHti                      :dWord;
  objHt                        :lpwStr;
  detailLevel,numCounters      :dWord;
  defCounter,numInstances      :lInt;
  codePage                    :dWord;
  perfTime,perfFreq            :int64
end;
//-----------------------------|----------------|----------------------|----------------------------
pPerfCounterDef=^tPerfCounterDef;
tPerfCounterDef=packed record
  byteLn,counterNti            :dWord;
  counterTit                  :lpwStr;
  counterHti                  :dWord;
  counterHt                    :lpwStr;
  defaultScale                :lInt;
  detailLevel,counterType,
  counterSize,counterOffset    :dWord
end;
//-----------------------------|----------------|----------------------|----------------------------
pPerfCounterBlock=^tPerfCounterBlock;
tPerfCounterBlock=packed record
  byteLn                      :dWord
end;
//-----------------------------|----------------|----------------------|----------------------------
pPerfInstanceDef=^tPerfInstanceDef;
tPerfInstanceDef=packed record
  byteLn,parentObjectTitleIndex,
  parentObjectInstance        :dWord;
  uniqueId                    :lInt;
  nameOffset,nameLength        :dWord
end;
//-----------------------------|----------------|----------------------|----------------------------

const
CPU_USAGE_IDX                =  6;          //Internal use
PROCESSOR_IDX                = 238;          //Internal use

//--------------------------------------------------------------------------------------------------
//tCpuUsageNTThrd:

type
tCpuUsageNTThrd=class(tThread)
  function usagePct(const aCpuId:byte):extended;
  procedure update;
private
  fCpuUsageNT                  :tCpuUsageNT;
  fPerfData                    :pPerfDataBlock;
  pot                          :pPerfObjType;
  pcd                          :pPerfCounterDef;
  fBufSize,fCpuCount          :integer;
  fCounters,fPrevCounters      :pAint64;
  fSysTime,fPrevSysTime        :int64;
protected
  procedure execute;override;
public
  count                        :lInt;
  constructor create(aCpuUsageNT:tCpuUsageNT);reintroduce;
  destructor destroy;override;
end;

constructor tCpuUsageNTThrd.create(aCpuUsageNT:tCpuUsageNT);//reintroduce;
begin inherited create(true);fCpuUsageNT:=aCpuUsageNT;freeOnTerminate:=true;
fCpuCount:=-1;fBufSize:=$2000;
getMem(fPerfData,fBufSize);fillChar(fPerfData^,fBufSize,0);resume
end;

destructor tCpuUsageNTThrd.destroy;//override;
begin freeMem(fPerfData);inherited destroy end;

procedure tCpuUsageNTThrd.execute;//override;
var i,c:lInt;
begin
with fCpuUsageNT do while not terminated do begin update;
  c:=fCpuCount;if c>MAX_CPU_COUNT then c:=MAX_CPU_COUNT;count:=c;
  for i:=0 to MAX_CPU_COUNT-1 do if i<c then data[i]:=self.usagePct(i) else data[i]:=0.0;
  synchronize(notifyChanges);
  sleep(THRD_TIMER_DELAY)
end
end;

function tCpuUsageNTThrd.usagePct(const aCpuId:byte):extended;
begin
if fPrevSysTime=fSysTime then result:=0 else
result:=(1-(fCounters[aCpuId]-fPrevCounters[aCpuId])/(fSysTime-fPrevSysTime))*100;
if result<0.0 then result:=0.0 else if result>100.0 then result:=100.0
end;

procedure tCpuUsageNTThrd.update;
var i,bs:integer;st:tFileTime;pcbI:pPerfCounterBlock;pidI:pPerfInstanceDef;
begin bs:=fBufSize;

while regQueryValueEx(HKEY_PERFORMANCE_DATA,pChar(intToStr(PROCESSOR_IDX)),nil,nil,pByte(fPerfData),@bs)=ERROR_MORE_DATA do begin
  inc(fBufSize,$1000);bs:=fBufSize;reallocMem(fPerfData,fBufSize)
end;

pot:=pPerfObjType(dWord(fPerfData)+fPerfData.headerLn);
for i:=1 to fPerfData.numObjectTypes do if pot.objNti<>PROCESSOR_IDX then
  pot:=pPerfObjType(dWord(pot)+pot.totalByteLn) else break;

if pot.objNti<>PROCESSOR_IDX then exit;

if fCpuCount<0 then begin fCpuCount:=pot.numInstances;
  getMem(fCounters,fCpuCount*sizeOf(int64));getMem(fPrevCounters,fCpuCount*sizeOf(int64));
end;

pcd:=pPerfCounterDef(dWord(pot)+pot.headerLn);
for i:=1 to pot.numCounters do if pcd.counterNti<>CPU_USAGE_IDX then
  pcd:=pPerfCounterDef(dWord(pcd)+pcd.byteLn) else break;

if pcd.counterNti<>CPU_USAGE_IDX then exit;

pidI:=pPerfInstanceDef(dWord(pot)+pot.defLn);
for i:=0 to fCpuCount-1 do begin
  pcbI:=pPerfCounterBlock(dWord(pidI)+pidI.byteLn);
  fPrevCounters[i]:=fCounters[i];
  fCounters[i]:=int64(pInt64(dWord(pcbI)+pcd.counterOffset)^);
  pidI:=pPerfInstanceDef(dWord(pcbI)+pcbI.byteLn);
end;

fPrevSysTime:=fSysTime;systemTimeToFileTime(fPerfData.sysTime,st);fSysTime:=int64(st)
end;

//--------------------------------------------------------------------------------------------------
//tCpuUsageNT:

constructor tCpuUsageNT.create;//virtual;
begin inherited create;reset;
thrd:=pointer(tCpuUsageNTThrd.create(self))
end;

destructor tCpuUsageNT.destroy;//override;
begin
if thrd<>nil then begin tCpuUsageNTThrd(thrd).terminate;thrd:=nil end;
inherited destroy
end;

procedure tCpuUsageNT.reset;//virtual;
var i:lInt;
begin for i:=0 to MAX_CPU_COUNT do data[i]:=0 end;

function tCpuUsageNT.usagePct(const aCpuId:lInt):extended;
begin if (aCpuId>-1) and (aCpuId<count) then result:=data[aCpuId] else result:=0 end;

function tCpuUsageNT.text(const aSep:str;const aAlsoWantAvrg:boolean=false):str;
var i,cnt:lInt;
begin result:='';cnt:=count-1;if not aAlsoWantAvrg then dec(cnt);
for i:=0 to cnt do begin
  if i>0 then result:=result+aSep;
  result:=result+intToStr(trunc(usagePct(i)))+'%'
end
end;

procedure tCpuUsageNT.notifyChanges;
begin if assigned(fOnChanges) then fOnChanges end;

end.



eksempel på aflæsning...:


unit Unit1;

interface

uses
  Windows, mbsCpuUsage, SysUtils, Classes, Graphics, Controls, Forms;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
  cpuUsage:tCpuUsageNT;
  procedure cpuUsageChanges;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
cpuUsage:=tCpuUsageNT.create;
cpuUsage.onChanges:=cpuUsageChanges
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
cpuUsage.free
end;

procedure TForm1.cpuUsageChanges;
begin
caption:=cpuUsage.text(',')
end;

end.
Avatar billede mtj111 Novice
11. august 2009 - 20:58 #13
Hej

Dit eksempel virker fremragende, og aflæser nu CPU-brugen korrekt :)

Jeg har dog fået eksemplet fra linket i mit første indlæg til at virke (i stedet for at have JwaWindows i sin Uses, skal man blot tilføje JwaPdhMsg og JwaPdh - så klager den ikke over at msxml mangler).
Det gode ved denne måde er, at man blot kan tilføje flere Performance Counters (hukommelse, disk, netværk mv.) med
PdhAddCounter(Query, '\\Computernavn\Processor(0)\% Processortid', 0, h);
Man kan derfor tilføje f.eks. 5 forskellige Counters man vil følge, og så kan man opdatere alle værdier med et kald (PdhCollectQueryData(Query);)

Din metode virker dog også perfekt, så du skal have nogle point!

Lægger du et svar? :)
Avatar billede mbsnet Nybegynder
12. august 2009 - 02:20 #14
ok, jeg vidste den virkede :-)
Den anden metode du beskriver lyder da også spændende.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester