ネットワーク経由でログファイルを高速に読み取るにはどうすればよいでしょうか?
-
21-09-2019 - |
質問
Delphi 2007 を使用しており、内部ネットワーク上のいくつかの場所からログ ファイルを読み取り、例外を表示するアプリケーションを持っています。これらのディレクトリには、数千のログ ファイルが含まれる場合があります。アプリケーションには、最新 n 日間のログ ファイルのみを読み取るオプションがあり、任意の日時範囲にすることもできます。
問題は、ログ ディレクトリを初めて読み取るときに、非常に時間がかかる (数分) 可能性があることです。2回目からはかなり速くなります。
できるだけ早くログファイルを読み取るためにコードを最適化するにはどうすればよいでしょうか?私はvCurrentFileを使用しています:TStringList を使用してファイルをメモリに保存します。こちらの方が速いと思うので、これは FileStream から更新されます。
以下にコードを示します。
リフレッシュ:ログファイルを読み取るメインループ
// In this function the logfiles are searched for exceptions. If a exception is found it is stored in a object.
// The exceptions are then shown in the grid
procedure TfrmMain.Refresh;
var
FileData : TSearchRec; // Used for the file searching. Contains data of the file
vPos, i, PathIndex : Integer;
vCurrentFile: TStringList;
vDate: TDateTime;
vFileStream: TFileStream;
begin
tvMain.DataController.RecordCount := 0;
vCurrentFile := TStringList.Create;
memCallStack.Clear;
try
for PathIndex := 0 to fPathList.Count - 1 do // Loop 0. This loops until all directories are searched through
begin
if (FindFirst (fPathList[PathIndex] + '\*.log', faAnyFile, FileData) = 0) then
repeat // Loop 1. This loops while there are .log files in Folder (CurrentPath)
vDate := FileDateToDateTime(FileData.Time);
if chkLogNames.Items[PathIndex].Checked and FileDateInInterval(vDate) then
begin
tvMain.BeginUpdate; // To speed up the grid - delays the guichange until EndUpdate
fPathPlusFile := fPathList[PathIndex] + '\' + FileData.Name;
vFileStream := TFileStream.Create(fPathPlusFile, fmShareDenyNone);
vCurrentFile.LoadFromStream(vFileStream);
fUser := FindDataInRow(vCurrentFile[0], 'User'); // FindData Returns the string after 'User' until ' '
fComputer := FindDataInRow(vCurrentFile[0], 'Computer'); // FindData Returns the string after 'Computer' until ' '
Application.ProcessMessages; // Give some priority to the User Interface
if not CancelForm.IsCanceled then
begin
if rdException.Checked then
for i := 0 to vCurrentFile.Count - 1 do
begin
vPos := AnsiPos(MainExceptionToFind, vCurrentFile[i]);
if vPos > 0 then
UpdateView(vCurrentFile[i], i, MainException);
vPos := AnsiPos(ExceptionHintToFind, vCurrentFile[i]);
if vPos > 0 then
UpdateView(vCurrentFile[i], i, HintException);
end
else if rdOtherText.Checked then
for i := 0 to vCurrentFile.Count - 1 do
begin
vPos := AnsiPos(txtTextToSearch.Text, vCurrentFile[i]);
if vPos > 0 then
UpdateView(vCurrentFile[i], i, TextSearch)
end
end;
vFileStream.Destroy;
tvMain.EndUpdate; // Now the Gui can be updated
end;
until(FindNext(FileData) <> 0) or (CancelForm.IsCanceled); // End Loop 1
end; // End Loop 0
finally
FreeAndNil(vCurrentFile);
end;
end;
UpdateView メソッド:ディスプレイグリッドに 1 行追加します
{: Update the grid with one exception}
procedure TfrmMain.UpdateView(aLine: string; const aIndex, aType: Integer);
var
vExceptionText: String;
vDate: TDateTime;
begin
if ExceptionDateInInterval(aLine, vDate) then // Parse the date from the beginning of date
begin
if aType = MainException then
vExceptionText := 'Exception'
else if aType = HintException then
vExceptionText := 'Exception Hint'
else if aType = TextSearch then
vExceptionText := 'Text Search';
SetRow(aIndex, vDate, ExtractFilePath(fPathPlusFile), ExtractFileName(fPathPlusFile), fUser, fComputer, aLine, vExceptionText);
end;
end;
行が日付範囲内にあるかどうかを判断するメソッド:
{: This compare exact exception time against the filters
@desc 2 cases: 1. Last n days
2. From - to range}
function TfrmMain.ExceptionDateInInterval(var aTestLine: String; out aDateTime: TDateTime): Boolean;
var
vtmpDate, vTmpTime: String;
vDate, vTime: TDateTime;
vIndex: Integer;
begin
aDateTime := 0;
vtmpDate := Copy(aTestLine, 0, 8);
vTmpTime := Copy(aTestLine, 10, 9);
Insert(DateSeparator, vtmpDate, 5);
Insert(DateSeparator, vtmpDate, 8);
if TryStrToDate(vtmpDate, vDate, fFormatSetting) and TryStrToTime(vTmpTime, vTime) then
aDateTime := vDate + vTime;
Result := (rdLatest.Checked and (aDateTime >= (Now - spnDateLast.Value))) or
(rdInterval.Checked and (aDateTime>= dtpDateFrom.Date) and (aDateTime <= dtpDateTo.Date));
if Result then
begin
vIndex := AnsiPos(']', aTestLine);
if vIndex > 0 then
Delete(aTestLine, 1, vIndex + 1);
end;
end;
ファイルの日付が範囲内にあるかどうかをテストします。
{: Returns true if the logtime is within filters range
@desc Purpose is to sort out logfiles that are no idea to parse (wrong day)}
function TfrmMain.FileDateInInterval(aFileTimeStamp: TDate): Boolean;
begin
Result := (rdLatest.Checked and (Int(aFileTimeStamp) >= Int(Now - spnDateLast.Value))) or
(rdInterval.Checked and (Int(aFileTimeStamp) >= Int(dtpDateFrom.Date)) and (Int(aFileTimeStamp) <= Int(dtpDateTo.Date)));
end;
解決
どのくらいの速あなたがなりたいのですか?あなたが本当に速くなりたい場合は、ファイルを読むためにWindowsネットワーク以外の何かを使用する必要があります。その理由は、あなたが(あなたがそれを読んで前回またはすべての行)のログファイルの最後の行を読みたい場合は、あなたがもう一度ファイル全体を読む必要がある。
あなたの質問では、問題は、あなたのディレクトリリストを列挙するために遅いことであると述べました。それはあなたの最初のボトルネックです。あなたが本当の高速になりたいなら、あなたはHTTPのいずれかに切り替える必要があるか、ログファイルが格納されているマシンにログサーバのいくつかの並べ替えを追加します。
HTTPを使用する利点は、範囲要求を行うと、ちょうどあなたが最後にそれを要求した以降に追加されたログファイルの新しい行を取得することができます。あなたは(あなたがHTTP圧縮を有効にする場合は特に)少ないデータを転送しているので、それは本当にパフォーマンスが向上しますし、あなたはまた、クライアント側のプロセスに少ないデータを持っています。
あなたはいくつかの種類のログサーバを追加する場合は、、その後、サーバは、データへのネイティブアクセス権を持つサーバー側の処理を、行うことができ、かつ唯一の日付範囲内にある行を返します。それを行うための簡単な方法は、単にいくつかの並べ替えのSQLデータベースにログを入れても、それに対してクエリを実行することがあります。
だから、どのくらいの速あなたが行きたいのですか?
他のヒント
問題は、あなたのロジックではなく、基礎となるファイルシステムます。
あなたがディレクトリに多くのファイルを入れたときにほとんどのファイルシステムが非常に遅くなります。これは、FATで非常に悪いですが、NTFSはまた、あなたがディレクトリ内のファイルの数千人を持っている場合は特に、それに苦しんでます。
あなたができる最善のは、年齢によって、たとえば、これらのディレクトリ構造を、再編成されます。
次に、各ディレクトリに最大で100個のファイルがいくつかあります。
- イェルーン