Como ler arquivos de log sobre a rede muito rápido?
-
21-09-2019 - |
Pergunta
Estou usando o Delphi 2007 e tenho um aplicativo que leia arquivos de log de vários lugares em uma rede interna e exibe exceções. Esses diretórios às vezes contêm milhares de arquivos de madeira. O aplicativo tem a opção de ler apenas arquivos de log a partir dos últimos dias, ele também pode estar em qualquer intervalo de tempo.
O problema é que a primeira vez que o diretório de log é lido, ele pode ser muito lento (vários minutos). A segunda vez é consideravelmente mais rápido.
Gostaria de saber como posso otimizar meu código para ler os arquivos de log o mais rápido possível? Estou usando um vcurrentfile: tStringList para armazenar o arquivo na memória. Isso é atualizado a partir de um FileStream, pois acho que isso é mais rápido.
Aqui está algum código:
Atualizar: O loop principal para ler arquivos de madeira
// 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;
Método da UpdateView: Adicione uma linha ao displayGrid
{: 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;
Método para decidir se a linha está em daterange:
{: 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;
Teste se o arquivo está dentro do intervalo:
{: 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;
Solução
Quão rápido você quer ser? Se você deseja ser muito rápido, precisa usar algo além do Windows Networking para ler os arquivos. O motivo é que, se você deseja ler a última linha de um arquivo de log (ou todas as linhas desde a última vez que a lê), precisará ler o arquivo inteiro novamente.
Na sua pergunta, você disse que o problema é que é lento para enumerar sua listagem de diretórios. Esse é o seu primeiro gargalo. Se você deseja ser muito rápido, precisará alternar para HTTP ou adicionar algum tipo de servidor de log à máquina em que os arquivos de log são armazenados.
A vantagem de usar o HTTP é que você pode fazer uma solicitação de intervalo e apenas obter as novas linhas do arquivo de log que foram adicionadas desde a última vez que você o solicitou. Isso realmente melhorará o desempenho, pois você está transferindo menos dados (especialmente se você ativar a compactação HTTP) e também terá menos dados para processar no lado do cliente.
Se você adicionar um servidor de log de algum tipo, esse servidor poderá fazer o processamento no lado do servidor, onde ele tem acesso nativo aos dados e retorna apenas as linhas que estão no intervalo. Uma maneira simples de fazer isso pode ser apenas colocar seus logs em um banco de dados SQL de algum tipo e, em seguida, executar consultas contra ele.
Então, quão rápido você quer ir?
Outras dicas
O problema não é sua lógica, mas o sistema de arquivos subjacente.
A maioria dos sistemas de arquivos fica muito lenta quando você coloca muitos arquivos em um diretório. Isso é muito ruim com a gordura, mas o NTFS também sofre disso, especialmente se você tiver milhares de arquivos em um diretório.
O melhor que você pode fazer é reorganizar essas estruturas de diretório, por exemplo, por idade.
Em seguida, tenha no máximo alguns 100 arquivos em cada diretório.
--Jeroen