Lista jednokierunkowa za mała pojemność. StringGrid się zawiesza przy wczytaniu dużej ilości danych.

0

Napisałem sobie taką oto klasę:

unit Logs;

{==============================================================================}

{$mode objfpc}{$H+}

{==============================================================================}

interface

{==============================================================================}

uses
  Classes, SysUtils;

{==============================================================================}

type
  TPriority = (pLowest, pLow, pNormal, pHigh, pHighest);

type
  TLogData = packed record
    Time     : String[20];
    Msg      : String[100];
    Priority : TPriority;
  end;

  PLogRec = ^TLogRec;
  TLogRec = record
    Next : PlogRec;
    Log  : TLogData;
  end;

{------------------------------------------------------------------------------}

type
  TLogClass = class(TObject)
  private
    First : PLogRec;
    Last  : PLogRec;
    FileName : String;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Add(aMsg : String; aPriority : TPriority);
    function GetFirst : PLogRec;
    procedure Clear;
    procedure LoadFromFile(aFileName : String);
    procedure SaveToFile(aFileName : String); overload;
    procedure SaveToFile; overload;
  end;

{==============================================================================}

implementation

{==============================================================================}

constructor TLogClass.Create;
begin
  inherited Create;
  FileName := StringReplace(DateTimeToStr(now), '-', '.', [rfReplaceAll]);
  FileName := StringReplace(FileName, ':', '-', [rfReplaceAll]);
  FileName := FileName + '.log';
  First := nil;
  Last := nil;
end;

{------------------------------------------------------------------------------}

destructor TLogClass.Destroy;
var
  ToDelete : PLogRec;
begin
  while First <> nil do
  begin
    ToDelete := First;
    First := First^.Next;
    Dispose(ToDelete);
  end;
  inherited Destroy;
end;

{------------------------------------------------------------------------------}

procedure TLogClass.Add(aMsg : String; aPriority : TPriority);
var
  NewLog : PLogRec;
begin
  New(NewLog);
  NewLog^.Log.Msg := aMsg;
  NewLog^.Log.Priority := aPriority;
  NewLog^.Log.Time := DateTimeToStr(Now);
  NewLog^.Next := nil;
  if First = nil then
  begin
    First := NewLog;
    Last := First;
  end
  else
  begin
    Last^.Next := NewLog;
    Last := NewLog;
  end;
end;

{------------------------------------------------------------------------------}

function TLogClass.GetFirst : PLogRec;
begin
  result := First;
end;

{------------------------------------------------------------------------------}

procedure TLogClass.Clear;
var
  ToDelete : PLogRec;
begin
  while First <> nil do
  begin
    ToDelete := First;
    First := First^.Next;
    Dispose(ToDelete);
  end;
  inherited Destroy;
end;

{------------------------------------------------------------------------------}

procedure TLogClass.LoadFromFile(aFileName : String);
var
  LogFile : File of TLogData;
  NewLog  : PLogRec;
begin
  AssignFile(LogFile, aFileName);
  Reset(LogFile);
  while not Eof(LogFile) do
  begin
    New(NewLog);
    Read(LogFile, NewLog^.Log);
    NewLog^.Next := nil;
    if First = nil then
    begin
      First := NewLog;
      Last := First;
    end
    else
    begin
      Last^.Next := NewLog;
      Last := NewLog;
    end;
  end;
end;

{------------------------------------------------------------------------------}

procedure TLogClass.SaveToFile(aFileName : String); overload;
var
  ToSave  : PLogRec;
  LogFile : File of TLogData;
begin
  AssignFile(LogFile, aFileName);
  Rewrite(LogFile);
  ToSave := GetFirst;
  while ToSave <> nil do
  begin
    Write(LogFile, ToSave^.Log);
    ToSave := ToSave^.Next
  end;;
  CloseFile(LogFile);
end;

{------------------------------------------------------------------------------}

procedure TLogClass.SaveToFile; overload;
var
  ToSave  : PLogRec;
  LogFile : File of TLogData;
begin
  AssignFile(LogFile, FileName);
  Rewrite(LogFile);
  ToSave := GetFirst;
  while ToSave <> nil do
  begin
    Write(LogFile, ToSave^.Log);
    ToSave := ToSave^.Next
  end;
  CloseFile(LogFile)
end;

{==============================================================================}

end.

I śmiga ona całkiem nieźle jednak mam 2 problemy:

  1. Ma ona ograniczoną pojemność, mieści niecałe 13 000 000 logów. Czy jest możliwe zwiększenie pojemności tej listy? Wiem że mogę zmienić długość wiadomości albo datę jakoś normalniej zapisywać ale nie da to aż tak dużego przyrostu pojemności jaki bym sobie życzył. Wiem również, że zmiana kompilatora z 32-bitowego na 64-bitowy też by pomogła bo po prostu bym miał więcej pamięci ale tego robić nie chcę. Na razie jedyne co wymyśliłem to gdy lista się przepełni i wyskoczy wyjątek o braku pamięci to w obsłudze wyjątku zapisać listę do pliku, następnie ją wyczyścić i lecieć zapisywanie od początku.

  2. Napisałem też programik do przeglądania logów zapisanych do pliku. Wygląda on tak:

unit Main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Grids,
  StdCtrls, Logs;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    OpenDialog1: TOpenDialog;
    StringGrid1: TStringGrid;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1    : TForm1;
  Log      : TLogClass;
  FileName : String;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormResize(Sender: TObject);
begin
  StringGrid1.Height := Form1.Height - Button1.Height * 2;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  tmpLog : PLogRec;
  i      : Word;
begin
  If OpenDialog1.Execute Then
  begin
    Log.LoadFromFile(OpenDialog1.FileName);
    i := 1;
    tmpLog := Log.GetFirst;
    while tmpLog <> nil do
    begin
      StringGrid1.RowCount := StringGrid1.RowCount + 1;
      StringGrid1.Cells[0,i] := IntToStr(i);
      StringGrid1.Cells[1,i] := tmpLog^.Log.Msg;
      StringGrid1.Cells[2,i] := tmpLog^.Log.Time;
      case tmpLog^.Log.Priority of
        pLowest  : StringGrid1.Cells[3,i] := 'LOWEST';
        pLow     : StringGrid1.Cells[3,i] := 'LOW';
        pNormal  : StringGrid1.Cells[3,i] := 'NORMAL';
        pHigh    : StringGrid1.Cells[3,i] := 'HIGH';
        pHighest : StringGrid1.Cells[3,i] := 'HIGHEST';
      end;
      tmpLog := tmpLog^.Next;
      Inc(i);
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  FreeAndNil(Log);
  Form1.Close;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Log := TLogClass.Create;
end;

end.

Z tym programem jest ten problem, że po wczytaniu za dużej ilości logów StringGrid nie może ich wyświetlić. W sensie tak jak by się zawiesił lub nie odświeżył. Jak to obejść bym mógł płynnie przeglądać nawet duże ilości logów? Myślałem nad wczytywaniem porcjami po np. 1000 logów jednak wtedy bym miał problem z cofaniem się. Bo jak wczytam 1000 logów i nie zamknę pliku tylko od razu to wyświetlę to jak dojdę do końca wyczyszczę StringGrida i wczytam kolejny 1000 to jeszcze spoko ale jak bym się chciał cofnąć to już może być problem. Mam pewien pomysł jak ten problem rozwiązać ale najpierw zapytam się czy sama idea jest dobra.

1
  1. to
FileName := StringReplace(DateTimeToStr(now), '-', '.', [rfReplaceAll]);
  FileName := StringReplace(FileName, ':', '-', [rfReplaceAll]);

możesz zastąpić jednym FormatDateTime
2. dlaczego Time : String[20]; a nie po prostu TDateTime
3. w metodzie Clear jest błąd - ma tam nie być inherited Destroy;
4. w Destroy powinno być wywołanie Clear zamiast copy & pastle
5. w SaveToFile powinna być tylko jedna linia postaci SaveToFile(FileName);

babubabu napisał(a):
  1. Ma ona ograniczoną pojemność, mieści niecałe 13 000 000 logów.

czyli jakieś 1,5GB pamięci - kto normalny trzyma tyle nikomu niepotrzebnych logów w pamięci?? Po co i na co?
Takie rzeczy zapisuje się w wielu plikach dzielonych w zależności od ilości danych na godzinę np. dziennie albo nawet godzinowo. Poza tym możesz się poruszać po pliku typowanym - Seek - zarówno w przód jak i w tył. Nie musisz wczytywać całego do pamięci. No i na koniec pokaż mi chociaż jedną osobę, która będzie na raz musiała mieć wyświetlone 13 MILIONÓW wpisów z logu podczas pracy - przecież tego fizycznie nikt nie jest w stanie ogarnąć

0

@abrakadaber
ad1. Dzieki.
ad2. Dzięki.
ad3. Poprawiłem zaraz po wrzuceniu wątku na forum.
ad4. Poprawiłem zaraz po wrzuceniu wątku na forum.
ad5. Poprawiłem zaraz po wrzuceniu watku na forum.

A co do wyświetlania tylu logów to masz rację błąd w planowaniu. Jednak załóżmy, że logowane są zdarzenia na serwerze który jest oblegany przez około 100000 osób na dobę. I chce znaleźć logowanie jednego klienta. Nawet jeśli znam przedział czasowy co do godziny kiedy się logował to wyświetlenie logów z samego podłączenia klientów z jednej godziny to będzie dość spora liczba danych i dlatego pojawiło się moje pytanie odnośnie przeglądarki logów i problemu ze StringGridem.

0

@abrakadaber większość rzeczy wytłumaczył; Ja ze swojej strony mogę polecić wykorzystanie plików amorficznych i strumienia, np. TFileStream;

Nawet jeśli znam przedział czasowy co do godziny kiedy się logował to wyświetlenie logów z samego podłączenia klientów z jednej godziny to będzie dość spora liczba danych i dlatego pojawiło się moje pytanie odnośnie przeglądarki logów i problemu ze StringGridem.

Nawet jeśli logów jest bardzo dużo, to nie musisz ich wszystkich pakować do komponentu - podziel je na jeszcze mniejsze przedziały (np. czasowe - co godzinę) i daj użytkownikowi możliwość nie tylko przewijania listy, ale także wskazania małego przedziału i z niego załadowanie odpowiednich logów; I tak nikt nie będzie w stanie lekko poruszać się po StringGrid z kilkoma tysiącami logów, już nie mówiąc o setkach tysięcy czy milionach.

0

jeśli chcesz to filtrować (np. konkretny user, konkretne zdarzenie, zakres dat) to ja bym sugerował zamiast pliku typowanego jakąkolwiek bazę - może być embedded, np. sqlite. Po pierwsze nie musisz na sztywno mieć rozmiaru pojedynczego wpisu, po drugie po dodaniu odpowiednich indeksów wyszukiwanie konkretnych zdarzeń będzie bardzo szybkie, po trzecie jak Ci się zamarzy to dbgrid wyświetli nawet i 100 milionów rekordów, chociaż będzie to bez sensu :p

1 użytkowników online, w tym zalogowanych: 0, gości: 1