source: trunk/UDriveScan.pas

Last change on this file was 41, checked in by chronos, 5 years ago
  • Modified: Build under Lazarus 2.0.
  • Modified: Used .lrj files instead of .lrt files.
  • Modified: Removed TemplateGenerics package.
File size: 13.8 KB
Line 
1unit UDriveScan;
2
3{$mode delphi}
4
5interface
6
7uses
8 Classes, SysUtils, Syncobjs, UBlockMap, Forms, DOM, UConfig, UPhysDrive,
9 UXMLUtils, Contnrs, ExtCtrls, DateUtils;
10
11type
12 TDriveScan = class;
13
14 TRunMode = (rmRead, rmWrite, rmNone, rmSpeed);
15 TExceptionEvent = procedure (Sender: TObject; E: Exception) of object;
16
17 { TScanThread }
18
19 TScanThread = class(TThread)
20 private
21 FOnException: TExceptionEvent;
22 public
23 Scan: TDriveScan;
24 ExceptionText: string;
25 procedure Execute; override;
26 property OnException: TExceptionEvent read FOnException write FOnException;
27 end;
28
29 { TDriveScanProfile }
30
31 TDriveScanProfile = class
32 SectorCount: Integer;
33 SectorStart: Integer;
34 SectorEnd: Integer;
35 Mode: TRunMode;
36 WritePattern: Byte;
37 WritePatternRandom: Boolean;
38 procedure LoadFromDriveInfo(DriveInfo: TDriveInfo);
39 end;
40
41 { TTestStream }
42
43 TTestStream = class(TStream)
44 public
45 function Read(var Buffer; Count: Longint): Longint; override;
46 function Write(const Buffer; Count: Longint): Longint; override;
47 function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
48 end;
49
50 { TSpeedValue }
51
52 TSpeedValue = record
53 public
54 Null: Boolean;
55 Max: Int64;
56 Average: Int64;
57 Values: array of Int64;
58 Min: Int64;
59 procedure Reset;
60 procedure UpdateValue(Value: Int64);
61 end;
62
63 { TDriveScan }
64
65 TDriveScan = class
66 private
67 FDriveName: string;
68 FOnChange: TNotifyEvent;
69 FOnTerminate: TNotifyEvent;
70 FSectorCurrent: Integer;
71 ScanThread: TScanThread;
72 LastExceptionMessage: string;
73 SpeedTimer: TTimer;
74 SpeedTimeLast: TDateTime;
75 SectorLast: Integer;
76 procedure DoOnExceptionSync;
77 procedure DoOnException(Sender: TObject; E: Exception);
78 procedure DoChange;
79 procedure DoTerminate;
80 function GetSectorCount: Integer;
81 procedure Run;
82 procedure SetDriveName(AValue: string);
83 procedure SetSectorCount(AValue: Integer);
84 procedure SpeedTimerExecute(Sender: TObject);
85 public
86 Lock: TCriticalSection;
87 BlockMap: TBlockMap;
88 TimeStart: TDateTime;
89 TimeEnd: TDateTime;
90 Terminated: Boolean;
91 DamagedBlockCount: Integer;
92 Mode: TRunMode;
93 WritePattern: Byte;
94 WritePatternRandom: Boolean;
95 SectorSize: Integer;
96 SectorStart: Integer;
97 SectorEnd: Integer;
98 SpeedSteps: array of TSpeedValue;
99 SpeedStepsCount: Integer;
100 function GetName: string;
101 function GetElapsedTime: TDateTime;
102 procedure Reset;
103 procedure Start;
104 procedure Stop;
105 procedure LoadProfile(Profile: TDriveScanProfile);
106 constructor Create;
107 destructor Destroy; override;
108 procedure SaveToNode(Node: TDOMNode);
109 procedure LoadFromNode(Node: TDOMNode);
110 property SectorCurrent: Integer read FSectorCurrent;
111 property DriveName: string read FDriveName write SetDriveName;
112 property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
113 property OnChange: TNotifyEvent read FOnChange write FOnChange;
114 property SectorCount: Integer read GetSectorCount write SetSectorCount;
115 end;
116
117 { TDriveScanList }
118
119 TDriveScanList = class(TObjectList)
120 procedure SaveToNode(Node: TDOMNode);
121 procedure LoadFromNode(Node: TDOMNode);
122 end;
123
124
125implementation
126
127resourcestring
128 SUnknownRunMode = 'Unknown run mode';
129
130{ TSpeedValue }
131
132procedure TSpeedValue.Reset;
133begin
134 Null := True;
135end;
136
137procedure TSpeedValue.UpdateValue(Value: Int64);
138var
139 Sum: Double;
140 I: Integer;
141begin
142 if Null then begin
143 Min := High(Int64);
144 Max := Low(Int64);
145 SetLength(Values, 0);
146 end;
147 Null := False;
148 if Value > Max then Max := Value;
149 // TODO: Computer average
150 SetLength(Values, Length(Values) + 1);
151 Values[Length(Values) - 1] := Value;
152 Sum := 0;
153 for I := 0 to Length(Values) - 1 do
154 Sum := Sum + Values[I];
155 Average := Trunc(Sum / Length(Values));
156 if Value < Min then Min := Value;
157end;
158
159{ TDriveScanProfile }
160
161procedure TDriveScanProfile.LoadFromDriveInfo(DriveInfo: TDriveInfo);
162begin
163 SectorCount := DriveInfo.SectorCount;
164 SectorStart := 0;
165 SectorEnd := DriveInfo.SectorCount - 1;
166end;
167
168{ TTestStream }
169
170function TTestStream.Read(var Buffer; Count: Longint): Longint;
171begin
172// if Random < 0.00001 then Result := 0
173// else
174 Result := Count;
175end;
176
177function TTestStream.Write(const Buffer; Count: Longint): Longint;
178begin
179 Result := 0;
180end;
181
182function TTestStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
183begin
184 Result := 0;
185end;
186
187{ TDriveScanList }
188
189procedure TDriveScanList.SaveToNode(Node: TDOMNode);
190var
191 I: Integer;
192 NewNode2: TDOMNode;
193begin
194 for I := 0 to Count - 1 do
195 with TDriveScan(Items[I]) do begin
196 NewNode2 := Node.OwnerDocument.CreateElement('Scan');
197 Node.AppendChild(NewNode2);
198 SaveToNode(NewNode2);
199 end;
200end;
201
202procedure TDriveScanList.LoadFromNode(Node: TDOMNode);
203var
204 Node2: TDOMNode;
205 NewScan: TDriveScan;
206begin
207 Count := 0;
208 Node2 := Node.FirstChild;
209 while Assigned(Node2) and (Node2.NodeName = 'Scan') do begin
210 NewScan := TDriveScan.Create;
211 NewScan.LoadFromNode(Node2);
212 Add(NewScan);
213 Node2 := Node2.NextSibling;
214 end;
215end;
216
217
218{ TScanThread }
219
220procedure TScanThread.Execute;
221begin
222 ExceptionText := '';
223 try
224 Scan.Run;
225 except
226 on E: Exception do
227 if Assigned(FOnException) then FOnException(Self, E);
228 end;
229end;
230
231{ TDriveScan }
232
233procedure TDriveScan.DoOnExceptionSync;
234begin
235 raise Exception.Create(LastExceptionMessage);
236end;
237
238procedure TDriveScan.DoOnException(Sender: TObject; E: Exception);
239begin
240 LastExceptionMessage := E.Message;
241 TThread(Sender).Synchronize(TThread(Sender), DoOnExceptionSync);
242end;
243
244procedure TDriveScan.DoChange;
245begin
246 if Assigned(FOnChange) then FOnChange(Self);
247end;
248
249procedure TDriveScan.DoTerminate;
250begin
251
252 if Assigned(FOnTerminate) then FOnTerminate(Self);
253end;
254
255function TDriveScan.GetSectorCount: Integer;
256begin
257 Result := BlockMap.SectorCount;
258end;
259
260function TDriveScan.GetElapsedTime: TDateTime;
261begin
262 if TimeEnd <> 0 then Result := TimeEnd - TimeStart
263 else Result := Now - TimeStart;
264end;
265
266procedure TDriveScan.Reset;
267var
268 I: Integer;
269begin
270 TimeStart := Now;
271 DamagedBlockCount := 0;
272 BlockMap.Clear;
273 FSectorCurrent := SectorStart;
274 SetLength(SpeedSteps, SpeedStepsCount);
275 for I := 0 to Length(SpeedSteps) - 1 do
276 SpeedSteps[I].Reset;
277end;
278
279procedure TDriveScan.Start;
280begin
281 if Terminated then begin
282 Terminated := False;
283 ScanThread := TScanThread.Create(True);
284 ScanThread.OnException := DoOnException;
285 ScanThread.Scan := Self;
286 ScanThread.Start;
287 SpeedTimeLast := Now;
288 SectorLast := SectorCurrent;
289 SpeedTimer.Enabled := True;
290 end;
291end;
292
293procedure TDriveScan.Run;
294var
295 F: TStream;
296 RealSize: Integer;
297 Buffer: Pointer;
298 BufferAligned: Pointer;
299 I: Integer;
300 Alignment: Integer;
301begin
302 try
303 Lock.Acquire;
304 Terminated := False;
305 if ConfigTest then F := TTestStream.Create
306 else begin
307 //if Mode = rmRead then F := TFileStreamEx.Create(DriveName, fmOpenRead, [ffDirect])
308 // else if Mode = rmWrite then F := TFileStreamEx.Create(DriveName, fmOpenReadWrite, [ffDirect]);
309 if Mode = rmRead then F := TFileStream.Create(DriveName, fmOpenRead)
310 else if Mode = rmWrite then F := TFileStream.Create(DriveName, fmOpenReadWrite);
311 end;
312 try
313 Alignment := SectorSize;
314 Buffer := GetMem(SectorSize + Alignment);
315 BufferAligned := Pointer(Int64(Buffer) - (Int64(Buffer) and (Alignment - 1)) + Alignment);
316 if Mode = rmWrite then begin
317 if not WritePatternRandom then
318 FillChar(BufferAligned^, SectorSize, WritePattern)
319 else for I := 0 to SectorSize - 1 do
320 PByte(Int64(BufferAligned) + I)^ := Random(256);
321 end;
322 while FSectorCurrent < SectorEnd do begin
323 try
324 Lock.Release;
325 DoChange;
326 F.Position := FSectorCurrent * SectorSize;
327 if Mode = rmRead then RealSize := F.Read(BufferAligned^, SectorSize)
328 else if Mode = rmWrite then RealSize := F.Write(BufferAligned^, SectorSize)
329 else raise Exception.Create(SUnknownRunMode);
330 finally
331 Lock.Acquire;
332 end;
333 if RealSize <> SectorSize then begin
334 BlockMap.Sectors[FSectorCurrent] := bsDamaged;
335 Inc(DamagedBlockCount);
336 end else begin
337 if Mode = rmRead then BlockMap.Sectors[FSectorCurrent] := bsRead
338 else if Mode = rmWrite then BlockMap.Sectors[FSectorCurrent] := bsWrite;
339 end;
340 Inc(FSectorCurrent);
341 if Terminated then Break;
342 end;
343 FreeMem(Buffer);
344 finally
345 F.Free;
346 end;
347 finally
348 Lock.Release;
349 end;
350 TimeEnd := Now;
351 DoChange;
352 Terminated := True;
353 ScanThread.Synchronize(DoTerminate);
354end;
355
356procedure TDriveScan.SetDriveName(AValue: string);
357begin
358 if FDriveName = AValue then Exit;
359 FDriveName := AValue;
360end;
361
362procedure TDriveScan.SetSectorCount(AValue: Integer);
363begin
364 if BlockMap.SectorCount = AValue then Exit;
365 BlockMap.SectorCount := AValue;
366end;
367
368procedure TDriveScan.SpeedTimerExecute(Sender: TObject);
369var
370 SpeedTimeCurrent: TDateTime;
371 Index: Integer;
372 Value: Int64;
373begin
374 SpeedTimeCurrent := Now;
375 Index := Trunc(SectorCurrent / SectorCount * SpeedStepsCount);
376 if Index >= Length(SpeedSteps) then Index := Length(SpeedSteps) - 1;
377 Value := Trunc((SectorCurrent - SectorLast) * SectorSize / ((SpeedTimeCurrent - SpeedTimeLast) / OneSecond));
378 if Value < 0 then Value := 0;
379 SpeedSteps[Index].UpdateValue(Value);
380 SpeedTimeLast := SpeedTimeCurrent;
381 SectorLast := SectorCurrent;
382end;
383
384function TDriveScan.GetName: string;
385begin
386 if Mode = rmNone then Result := 'None'
387 else if Mode = rmRead then Result := 'Read'
388 else if Mode = rmWrite then begin
389 if WritePatternRandom then Result := 'Write pattern random'
390 else Result := 'Write pattern $' + IntToHex(WritePattern, 2);
391 end else Result := '';
392end;
393
394procedure TDriveScan.Stop;
395begin
396 if not Terminated then begin
397 SpeedTimer.Enabled := False;
398 Terminated := True;
399 ScanThread.Terminate;
400 ScanThread.WaitFor;
401 FreeAndNil(ScanThread);
402 end;
403end;
404
405procedure TDriveScan.LoadProfile(Profile: TDriveScanProfile);
406begin
407 SectorStart := Profile.SectorStart;
408 SectorEnd := Profile.SectorEnd;
409 Mode := Profile.Mode;
410 WritePattern := Profile.WritePattern;
411 WritePatternRandom := Profile.WritePatternRandom;
412end;
413
414constructor TDriveScan.Create;
415begin
416 Lock := TCriticalSection.Create;
417 BlockMap := TBlockMap.Create;
418 SpeedTimer := TTimer.Create(nil);
419 SpeedTimer.Interval := 500;
420 SpeedTimer.Enabled := False;
421 SpeedTimer.OnTimer := SpeedTimerExecute;
422 SpeedStepsCount := 500;
423 SectorSize := 4096;
424 Terminated := True;
425 Reset;
426 Mode := rmNone;
427end;
428
429destructor TDriveScan.Destroy;
430begin
431 Stop;
432 FreeAndNil(SpeedTimer);
433 FreeAndNil(BlockMap);
434 FreeAndNil(Lock);
435 inherited Destroy;
436end;
437
438procedure TDriveScan.SaveToNode(Node: TDOMNode);
439var
440 NewNode: TDOMNode;
441 NewNode2: TDOMNode;
442 NewNode3: TDOMNode;
443 I: Integer;
444begin
445 with Node do begin
446 WriteInteger(Node, 'DamagedBlockCount', DamagedBlockCount);
447 WriteInteger(Node, 'RunMode', Integer(Mode));
448 WriteInteger(Node, 'WritePattern', WritePattern);
449 WriteBoolean(Node, 'WritePatternRandom', WritePatternRandom);
450 WriteString(Node, 'DriveName', DriveName);
451 WriteDateTime(Node, 'TimeStart', TimeStart);
452 WriteDateTime(Node, 'TimeEnd', TimeEnd);
453 WriteInteger(Node, 'SectorSize', SectorSize);
454 WriteInteger(Node, 'SectorCount', SectorCount);
455 WriteInteger(Node, 'SectorStart', SectorStart);
456 WriteInteger(Node, 'SectorEnd', SectorEnd);
457 WriteInteger(Node, 'SectorCurrent', FSectorCurrent);
458
459 NewNode := OwnerDocument.CreateElement('SectorMap');
460 AppendChild(NewNode);
461 BlockMap.SaveToNode(NewNode);
462
463 NewNode := OwnerDocument.CreateElement('Speed');
464 AppendChild(NewNode);
465 WriteInt64(NewNode, 'Count', Length(SpeedSteps));
466 NewNode2 := OwnerDocument.CreateElement('Steps');
467 NewNode.AppendChild(NewNode2);
468 for I := 0 to Length(SpeedSteps) - 1 do
469 if not SpeedSteps[I].Null then begin
470 NewNode3 := OwnerDocument.CreateElement('Step');
471 NewNode2.AppendChild(NewNode3);
472 WriteInteger(NewNode3, 'Index', I);
473 WriteInt64(NewNode3, 'Avg', SpeedSteps[I].Average);
474 WriteInt64(NewNode3, 'Min', SpeedSteps[I].Min);
475 WriteInt64(NewNode3, 'Max', SpeedSteps[I].Max);
476 end;
477 end;
478end;
479
480procedure TDriveScan.LoadFromNode(Node: TDOMNode);
481var
482 NewNode: TDOMNode;
483 NewNode2: TDOMNode;
484 NewNode3: TDOMNode;
485 I: Integer;
486begin
487 with Node do begin
488 DamagedBlockCount := ReadInteger(Node, 'DamagedBlockCount', 0);
489 Mode := TRunMode(ReadInteger(Node, 'RunMode', 0));
490 WritePattern := ReadInteger(Node, 'WritePattern', 0);
491 WritePatternRandom := ReadBoolean(Node, 'WritePatternRandom', False);
492 DriveName := ReadString(Node, 'DriveName', '');
493 TimeStart := ReadDateTime(Node, 'TimeStart', 0);
494 TimeEnd := ReadDateTime(Node, 'TimeEnd', 0);
495 SectorSize := ReadInteger(Node, 'SectorSize', 512);
496 SectorCount := ReadInteger(Node, 'SectorCount', 0);
497 SectorStart := ReadInteger(Node, 'SectorStart', 0);
498 SectorEnd := ReadInteger(Node, 'SectorEnd', SectorCount - 1);
499 FSectorCurrent := ReadInteger(Node, 'SectorCurrent', 0);
500
501 NewNode := FindNode('SectorMap');
502 if Assigned(NewNode) then
503 BlockMap.LoadFromNode(NewNode);
504
505 NewNode := FindNode('Speed');
506 if Assigned(NewNode) then begin
507 SpeedStepsCount := ReadInt64(NewNode, 'Count', 0);
508 SetLength(SpeedSteps, SpeedStepsCount);
509 NewNode2 := NewNode.FindNode('Steps');
510 if Assigned(NewNode2) then begin
511 NewNode3 := NewNode2.FirstChild;
512 while Assigned(NewNode3) and (NewNode3.NodeName = 'Step') do begin
513 I := ReadInteger(NewNode3, 'Index', 0);
514 SpeedSteps[I].Average := ReadInt64(NewNode3, 'Avg', 0);
515 SpeedSteps[I].Min := ReadInt64(NewNode3, 'Min', 0);
516 SpeedSteps[I].Max := ReadInt64(NewNode3, 'Max', 0);
517 SpeedSteps[I].Null := False;
518 NewNode3 := NewNode3.NextSibling;
519 end;
520 end;
521 end;
522end;
523end;
524
525end.
526
Note: See TracBrowser for help on using the repository browser.