Question

J'utilise Delphi 2007 et ont une application qui journal de lecture-fichiers de plusieurs emplacements sur un réseau interne et d'affichage des exceptions. Ces répertoires contiennent parfois des milliers de fichiers log. L'application dispose d'une option de lecture seule fichiers log des n derniers jours bit il peut aussi être dans une plage datetime.

Le problème est que la première fois le répertoire du journal est lu, il peut être très lent (plusieurs minutes). La deuxième fois, il est beaucoup plus rapide.

Je me demande comment je peux optimiser mon code pour lire les fichiers log aussi vite que possible? J'utilise un vCurrentFile: TStringList pour stocker le fichier dans la mémoire. Cela est mis à jour à partir d'un FileStream que je pense que cela est plus rapide.

Voici un code:

Actualiser : La boucle principale pour lire les fichiers log-

// 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éthode de UpdateView : ajouter une ligne à la 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éthode de décider si la ligne est en 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;

Test si le filedate est à l'intérieur plage:

{: 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;
Était-ce utile?

La solution

À quelle vitesse voulez-vous être? Si vous voulez être vraiment rapide, vous devez utiliser autre chose que les fenêtres en réseau pour lire les fichiers. La raison est que si vous voulez lire la dernière ligne d'un fichier journal (ou toutes les lignes depuis la dernière fois que vous lisez) alors vous avez besoin de relire tout le fichier.

Dans votre question, vous avez dit que le problème est qu'il est lent à énumérer la liste des répertoires. C'est votre premier goulot d'étranglement. Si vous voulez être vraiment rapide, vous devez soit passer à HTTP ou ajouter une sorte de serveur de journal à la machine où les fichiers journaux sont stockés.

L'avantage d'utiliser HTTP est que vous pouvez faire une demande de plage et juste obtenir les nouvelles lignes du fichier journal qui ont été ajoutés depuis la dernière l'a demandé. Cela va vraiment améliorer les performances puisque vous transférez moins de données (en particulier si vous activez la compression HTTP) et vous avez également moins de données à traiter sur le côté client.

Si vous ajoutez un serveur de journal de quelque sorte, ce serveur peut faire le traitement du côté du serveur, où il dispose d'un accès natif aux données, et seulement renvoyer les lignes qui se trouvent dans la plage de dates. Une façon simple de le faire peut-être juste à mettre vos journaux dans une base de données SQL de quelque sorte, puis exécuter des requêtes contre.

Alors, comment voulez-rapide voulez-vous aller?

Autres conseils

Le problème est pas votre logique, mais le système de fichiers sous-jacent.

La plupart des systèmes de fichiers deviennent très lent lorsque vous mettez beaucoup de fichiers dans un répertoire. Ceci est très mauvais avec FAT, NTFS, mais souffre aussi d'elle, surtout si vous avez des milliers de fichiers dans un répertoire.

Le mieux que vous pouvez faire est de réorganiser les structures de répertoires, par exemple par âge.

Ensuite, avoir au plus un couple de 100 fichiers dans chaque répertoire.

- jeroen

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top