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;
Foi útil?

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top