Servus,
also... ich habe ein heftiges Performanceproblem mit TChart. Folgende Situation:
Wir erstellen Performancedaten für unsere Anwendung. In den Logdateien finden sich verschiedene Einträge, wie z.B. Messzeitpunkt, Dauer in ms, Username, IP- Adresse, JobId, SessionId ... In den Logdateien können insgesamt durchaus auch gut und gerne 500.000 Zeilen stehen.
In dem Chart müssen nun die Reaktionszeiten angezeigt werden und zwar nach verschiedenen Kriterien gefiltert. Die Standardeinstellung sieht vor, dass für jeden "Jobtyp" (bis zu 120 verschiedene) eine eigene Series benötigt wird und immer alle Messwerte in PointSeries dargestellt werden. Somit habe ich auf der X- Achse den Zeitpunkt der Messung und auf der Y- Achse die Dauer des Jobs in ms. Der Anwender hat die Möglichkeit, selber zu bestimmen, nach welchen Kriterien die Messwerte gruppiert werden sollen (JobId, BenutzerId, TerminalId, SessionId, LogLevel).
Alles in allem verwende ich mehrere Charts, um verschiedene Auswertungen zugleich Anzeigen zu können. So zum Beispiel die absolute Reaktionszeit, die relative Reaktionszeit, die gemittelte Reaktionszeit zu einem bestimmten Zeitpunkt, eine Darstellung der Abhängigkeit von Dauer und der Anzahl gleichzeitig aktiver Jobs,...
Die Daten für die Charts habe ich in einem (bzw. drei) TkbmMemTables. Ich hatte schon einmal versucht, die MemTables zu klonen und jeweils mit Filtern die verschiedenen Chart.Series zu bedienen, aber das war ein absoluter Reinfall. Aktuell versuche ich die Series mit AddXY zu befüllen, aber da geht die Performance doch ziemlich in die Knie.
Mein Problem ist, dass die Übernehmen der Messwerte und die Anzeige der Daten seeeehr lange dauert.
Ich habe schon einige Tweaks, die ich auf den Supportseiten von steema.com gefunden habe, eingebaut, aber es hat nicht einmal marginale Auswirkungen gehabt ^^
Folgende Funktion verwende ich, um zwei der Charts "vorzubereiten":
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128:
| procedure TMainForm.PrepareCharts(const aGroupIndex: Integer); var loc_i : Integer; loc_OptFrame : TOptionFrame; begin
case aGroupIndex of 0 : begin loc_OptFrame := optLogLevel; end; 1 : begin loc_OptFrame := optSessionId; end; 2 : begin loc_OptFrame := optUserId; end; 3 : begin loc_OptFrame := optTerminalId; end; else loc_OptFrame := optJobId; end;
chtResponseTime.Canvas.ReferenceCanvas.Pen.OwnerCriticalSection := nil; if loc_OptFrame.CheckedCount > 0 then begin for loc_i := 0 to loc_OptFrame.Count - 1 do if loc_OptFrame.GetChecked(loc_i) then begin with chtResponseTime do begin AddSeries(TPointSeries.Create(Self));
with (Series[Pred(SeriesCount)] as TPointSeries) do begin Active := False; AutoRepaint := False; LinePen.OwnerCriticalSection := nil; Title := loc_OptFrame.GetOption(loc_i); Name := 'chtResponseTimeSeries' + IntToStr(SeriesCount); Identifier := IntToStr(Pred(SeriesCount)); XValues.DateTime := True; XValues.Order := loNone; SeriesColor := CON_COLORSET[Pred(SeriesCount) mod 21]; VertAxis := aBothVertAxis; HorizAxis := aBothHorizAxis; Pointer.Style := psDiamond; Pointer.HorizSize := 3; Pointer.VertSize := 3; end; end;
with chtRelResponseTime do begin AddSeries(TPointSeries.Create(Self));
with (Series[Pred(SeriesCount)] as TPointSeries) do begin Active := False; AutoRepaint := False; LinePen.OwnerCriticalSection := nil; Title := loc_OptFrame.GetOption(loc_i); Name := 'chtRelResponseTimeSeries' + IntToStr(SeriesCount); Identifier := IntToStr(Pred(SeriesCount)); XValues.DateTime := True; XValues.Order := loNone; SeriesColor := CON_COLORSET[Pred(SeriesCount) mod 21]; VertAxis := aBothVertAxis; HorizAxis := aBothHorizAxis; Pointer.Style := psDiamond; Pointer.HorizSize := 3; Pointer.VertSize := 3; end; end; end; end else begin for loc_i := 0 to loc_OptFrame.Count - 1 do begin with chtResponseTime do begin AddSeries(TPointSeries.Create(Self));
with (Series[Pred(SeriesCount)] as TPointSeries) do begin Active := False; AutoRepaint := False; LinePen.OwnerCriticalSection := nil; Title := loc_OptFrame.GetOption(loc_i); Name := 'chtResponseTimeSeries' + IntToStr(SeriesCount); Identifier := IntToStr(Pred(SeriesCount)); XValues.DateTime := True; XValues.Order := loNone; SeriesColor := CON_COLORSET[Pred(SeriesCount) mod 21]; VertAxis := aBothVertAxis; HorizAxis := aBothHorizAxis; Pointer.Style := psDiamond; Pointer.HorizSize := 3; Pointer.VertSize := 3; end; end;
with chtRelResponseTime do begin AddSeries(TPointSeries.Create(Self));
with (Series[Pred(SeriesCount)] as TPointSeries) do begin Active := False; AutoRepaint := False; LinePen.OwnerCriticalSection := nil; Title := loc_OptFrame.GetOption(loc_i); Name := 'chtRelResponseTimeSeries' + IntToStr(SeriesCount); Identifier := IntToStr(Pred(SeriesCount)); XValues.DateTime := True; XValues.Order := loNone; SeriesColor := CON_COLORSET[Pred(SeriesCount) mod 21]; VertAxis := aBothVertAxis; HorizAxis := aBothHorizAxis; Pointer.Style := psDiamond; Pointer.HorizSize := 3; Pointer.VertSize := 3; end; end; end; end;
chtRelResponseTime.Tag := aGroupIndex; chtResponseTime.Tag := aGroupIndex; Forms.Application.ProcessMessages; end; |
In der folgenden Funktion befülle ich die Series, denke, dass hier das meiste Verbesserungspotential liegen könnte... aber ich habe keine Ahnung, wo ich da ansetzen muss.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158:
| procedure TMainForm.PlotResponseTimeChart(const aGroupIndex: Integer); var loc_i : Integer; loc_filter : String; loc_sec : Integer; loc_time: string; begin DisplayCurrentJob('Plotting Response Time Graph');
tblLogData.DisableControls; tblTimeChart.DisableControls; try tblLogData.Filtered := False;
loc_filter := GenerateFilterString; if loc_filter <> EmptyStr then begin DisplayCurrentJob('Applying Filter to Dataset'); tblLogData.Filter := loc_filter; tblLogData.Filtered := True; end;
FFilterCount := tblLogData.RecordCount; FProgress.Pos := 0; FProgress.Max := FFilterCount; DisplayCurrentJob('Plotting Response Time Graph');
if FFilterCount <> FRecordCount then begin if tblTimeChart.Active then tblTimeChart.EmptyTable; tblTimeChart.Close; tblTimeChart.Open; end; tblLogData.First; while not tblLogData.Eof do begin case aGroupIndex of 0 : loc_i := GetChartSeriesNumber( chtResponseTime, tblLogData.FieldByName('LogLevel').AsString ); 1 : loc_i := GetChartSeriesNumber( chtResponseTime, tblLogData.FieldByName('Session').AsString ); 2 : loc_i := GetChartSeriesNumber( chtResponseTime, tblLogData.FieldByName('UserId').AsString ); 3 : loc_i := GetChartSeriesNumber( chtResponseTime, tblLogData.FieldByName('TerminalId').AsString ); else loc_i := GetChartSeriesNumber( chtResponseTime, tblLogData.FieldByName('JobId').AsString ); end;
if loc_i >= 0 then begin with chtResponseTime.SeriesList[loc_i] do if rbtStartTime.Checked then AddXY( tblLogData.FieldByName('StartDateTime').AsFloat, tblLogData.FieldByName('Duration').AsInteger, CreateLabel ) else AddXY( tblLogData.FieldByName('EndDateTime').AsFloat, tblLogData.FieldByName('Duration').AsInteger, CreateLabel ); end;
if (FFilterCount <> FRecordCount) then begin with tblTimeChart do begin for loc_sec := 0 to SecondsBetween( tblLogData.FieldByName('StartDateTime').AsDateTime, tblLogData.FieldByName('EndDateTime').AsDateTime ) do begin loc_time := DateTimeToStr( tblLogData.FieldByName('StartDateTime').AsDateTime + (loc_sec * OneSecond) ); if Locate('DateTimeString', loc_time, []) then begin Edit; FieldByName('JobCount').AsInteger := tblTimeChart.FieldByName('JobCount').AsInteger + 1; end else begin Append; FieldByName('DateTimeString').AsString := loc_time; FieldByName('DateTime').AsDateTime := tblLogData.FieldByName('StartDateTime').AsDateTime + (loc_sec * OneSecond); FieldByName('JobCount').AsInteger := 1; FieldByName('UniqueJobCount').AsInteger := 0; FieldByName('MiddledDuration').AsInteger := 0; end; end;
if (rbtStartTime.Checked) then loc_time := DateTimeToStr(tblLogData.FieldByName('StartDateTime').AsDateTime) else loc_time := DateTimeToStr(tblLogData.FieldByName('EndDateTime').AsDateTime);
if Locate('DateTimeString', loc_time, []) then begin Edit; FieldByName('MiddledDuration').AsInteger := ( ( FieldByName('MiddledDuration').AsInteger * FieldByName('UniqueJobCount').AsInteger ) + tblLogData.FieldByName('Duration').AsInteger ) div Succ(FieldByName('UniqueJobCount').AsInteger); FieldByName('UniqueJobCount').AsInteger := FieldByName('UniqueJobCount').AsInteger + 1; end else begin Append; FieldByName('DateTimeString').AsString := loc_time; if rbtStartTime.Checked then FieldByName('DateTime').AsDateTime := tblLogData.FieldByName('StartDateTime').AsDateTime else FieldByName('DateTime').AsDateTime := tblLogData.FieldByName('EndDateTime').AsDateTime; FieldByName('UniqueJobCount').AsInteger := 1; FieldByName('MiddledDuration').AsInteger := tblLogData.FieldByName('Duration').AsInteger; end; end; end;
FProgress.Pos := FProgress.Pos + 1; tblLogData.Next; end; finally if (tblTimeChart.State in [dsEdit, dsInsert]) then tblTimeChart.Post; tblTimeChart.First; tblTimeChart.EnableControls; tblLogData.Filtered := False; tblLogData.Filter := EmptyStr; tblLogData.First; tblLogData.EnableControls; end; FProgress.Pos := FProgress.Max; if FFilterCount <> FRecordCount then stbMain.Panels[2].Text := Format('%d of %d records filtered', [FFilterCount, FRecordCount]) ;
Forms.Application.ProcessMessages; end; |
Hat von Euch noch jemand eine Idee? Bin so langsam am Verzweifeln ^^