ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/IBSQLMonitor.pas
Revision: 315
Committed: Thu Feb 25 11:56:36 2021 UTC (3 years, 9 months ago) by tony
Content type: text/x-pascal
File size: 33672 byte(s)
Log Message:
Updated for IBX 4 release

File Contents

# User Rev Content
1 tony 209 {************************************************************************}
2     { }
3     { Borland Delphi Visual Component Library }
4     { InterBase Express core components }
5     { }
6     { Copyright (c) 1998-2000 Inprise Corporation }
7     { }
8     { InterBase Express is based in part on the product }
9     { Free IB Components, written by Gregory H. Deatz for }
10     { Hoagland, Longo, Moran, Dunst & Doukas Company. }
11     { Free IB Components is used under license. }
12     { }
13     { The contents of this file are subject to the InterBase }
14     { Public License Version 1.0 (the "License"); you may not }
15     { use this file except in compliance with the License. You }
16     { may obtain a copy of the License at http://www.Inprise.com/IPL.html }
17     { Software distributed under the License is distributed on }
18     { an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either }
19     { express or implied. See the License for the specific language }
20     { governing rights and limitations under the License. }
21     { The Original Code was created by InterBase Software Corporation }
22     { and its successors. }
23     { Portions created by Inprise Corporation are Copyright (C) Inprise }
24     { Corporation. All Rights Reserved. }
25     { Contributor(s): Jeff Overcash }
26     { }
27     { IBX For Lazarus (Firebird Express) }
28     { Contributor: Tony Whyman, MWA Software http://www.mwasoftware.co.uk }
29     { Portions created by MWA Software are copyright McCallum Whyman }
30     { Associates Ltd 2011 - 2018 }
31     { }
32     {************************************************************************}
33    
34     {
35     This unit has been significantly revised for the Lazarus port. Specially,
36     there was a need to re-organise the code to isolate the Windows specific
37     IPC and to introduce SV5 IPC as an alternative for Linux and other platforms.
38     }
39    
40     unit IBSQLMonitor;
41    
42     {$Mode Delphi}
43    
44     {$codepage UTF8}
45    
46     interface
47    
48     uses
49 tony 315 IB, IBUtils, IBSQL, IBCustomDataSet, IBDatabase, DB, IBInternals,
50     SysUtils, Classes,
51 tony 209 {$IFDEF WINDOWS }
52     Windows
53     {$ENDIF}
54 tony 215 {$IFDEF UNIX}
55 tony 216 unix
56 tony 215 {$ENDIF}
57 tony 209 ;
58    
59     {Note that the original inter-thread communication between the Reader Thread and
60     the ISQL Monitor used the Windows PostMessage interface. This is currently not
61     useable under the FPC RTL as AllocateHWnd is not functional. It has been replaced
62     by the use of the Synchronize method.}
63    
64     {$IFDEF WINDOWS}
65     {$DEFINE USE_WINDOWS_IPC}
66     {$ENDIF}
67    
68     {$IFDEF UNIX}
69     {$DEFINE USE_SV5_IPC}
70     {$ENDIF}
71    
72     {$IFDEF LINUX}
73     {$DEFINE HAS_SEMTIMEDOP}
74     {$ENDIF}
75    
76     type
77     TIBCustomSQLMonitor = class;
78    
79     TSQLEvent = procedure(EventText: String; EventTime : TDateTime) of object;
80    
81     { TIBCustomSQLMonitor }
82    
83     TIBCustomSQLMonitor = class(TComponent)
84     private
85     FOnSQLEvent: TSQLEvent;
86     FTraceFlags: TTraceFlags;
87     FEnabled: Boolean;
88     procedure SetEnabled(const Value: Boolean);
89     protected
90     procedure ReleaseObject; {Called from Writer Thread}
91     procedure ReceiveMessage(Msg: TObject); {Called from Reader Thread}
92     property OnSQL: TSQLEvent read FOnSQLEvent write FOnSQLEvent;
93     property TraceFlags: TTraceFlags read FTraceFlags write FTraceFlags;
94     property Enabled : Boolean read FEnabled write SetEnabled default true;
95     public
96     constructor Create(AOwner: TComponent); override;
97     destructor Destroy; override;
98     procedure Release;
99     end;
100    
101     { TIBSQLMonitor }
102    
103     TIBSQLMonitor = class(TIBCustomSQLMonitor)
104     published
105     property OnSQL;
106     property TraceFlags;
107     property Enabled;
108     end;
109    
110     IIBSQLMonitorHook = interface
111     ['{CF65434C-9B75-4298-BA7E-E6B85B3C769D}']
112     procedure RegisterMonitor(SQLMonitor : TIBCustomSQLMonitor);
113     procedure UnregisterMonitor(SQLMonitor : TIBCustomSQLMonitor);
114     procedure ReleaseMonitor(Arg : TIBCustomSQLMonitor);
115     procedure SQLPrepare(qry: TIBSQL);
116     procedure SQLExecute(qry: TIBSQL);
117     procedure SQLFetch(qry: TIBSQL);
118     procedure DBConnect(db: TIBDatabase);
119     procedure DBDisconnect(db: TIBDatabase);
120     procedure TRStart(tr: TIBTransaction);
121     procedure TRCommit(tr: TIBTransaction);
122     procedure TRCommitRetaining(tr: TIBTransaction);
123     procedure TRRollback(tr: TIBTransaction);
124     procedure TRRollbackRetaining(tr: TIBTransaction);
125     procedure ServiceAttach(service: TIBMonitoredService); overload;
126     procedure ServiceDetach(service: TIBMonitoredService); overload;
127     procedure ServiceQuery(service: TIBMonitoredService); overload;
128     procedure ServiceStart(service: TIBMonitoredService); overload;
129     procedure ServiceAttach(service: TIBXMonitoredConnection); overload;
130     procedure ServiceDetach(service: TIBXMonitoredConnection); overload;
131     procedure ServiceQuery(service: TIBXMonitoredService); overload;
132     procedure ServiceStart(service: TIBXMonitoredService); overload;
133     procedure SendMisc(Msg : String);
134     function GetTraceFlags : TTraceFlags;
135     function GetMonitorCount : Integer;
136     procedure SetTraceFlags(const Value : TTraceFlags);
137     function GetEnabled : boolean;
138     procedure SetEnabled(const Value : Boolean);
139     property TraceFlags: TTraceFlags read GetTraceFlags write SetTraceFlags;
140     property Enabled : Boolean read GetEnabled write SetEnabled;
141     end;
142    
143    
144     function MonitorHook: IIBSQLMonitorHook;
145     procedure EnableMonitoring;
146     procedure DisableMonitoring;
147     function MonitoringEnabled: Boolean;
148    
149     implementation
150    
151     uses
152 tony 291 contnrs, syncobjs, CustApp, IBMessages
153 tony 209 {$IFDEF USE_SV5_IPC}
154     ,ipc, Errors, baseunix
155     {$ENDIF};
156    
157    
158     const
159     cMonitorHookSize = 1024;
160     cMsgWaitTime = 1000;
161     cWriteMessageAvailable = 'WriterMsgQueue';
162    
163     type
164     { There are two possible objects. One is a trace message object.
165     This object holds the flag of the trace type plus the message.
166     The second object is a Release object. It holds the handle that
167     the CM_RELEASE message is to be queued to. }
168    
169     { TTraceObject }
170    
171     TTraceObject = Class(TObject)
172     FDataType : TTraceFlag;
173     FMsg : String;
174     FTimeStamp : TDateTime;
175     public
176     constructor Create(Msg : String; DataType : TTraceFlag); overload;
177     constructor Create(obj : TTraceObject); overload;
178     constructor Create(obj : TTraceObject; MsgOffset, MsgLen: integer); overload;
179     end;
180    
181     { TReleaseObject }
182    
183     TReleaseObject = Class(TObject)
184     FMonitor : TIBCustomSQLMonitor;
185     public
186     constructor Create(Monitor : TIBCustomSQLMonitor);
187     end;
188    
189     {$IFDEF USE_SV5_IPC}
190     {$I sv5ipc.inc}
191     {$ENDIF}
192     {$IFDEF USE_WINDOWS_IPC}
193     {$I winipc.inc}
194     {$ENDIF}
195    
196     type
197    
198     { TIBSQLMonitorHook }
199    
200     TIBSQLMonitorHook = class(TInterfacedObject, IIBSQLMonitorHook)
201     private
202     FGlobalInterface: TGlobalInterface;
203     FTraceFlags: TTraceFlags;
204     FEnabled: Boolean;
205     protected
206     procedure WriteSQLData(Text: String; DataType: TTraceFlag);
207     public
208     constructor Create;
209     destructor Destroy; override;
210     procedure RegisterMonitor(SQLMonitor : TIBCustomSQLMonitor);
211     procedure UnregisterMonitor(SQLMonitor : TIBCustomSQLMonitor);
212     procedure ReleaseMonitor(Arg : TIBCustomSQLMonitor);
213     procedure SQLPrepare(qry: TIBSQL); virtual;
214     procedure SQLExecute(qry: TIBSQL); virtual;
215     procedure SQLFetch(qry: TIBSQL); virtual;
216     procedure DBConnect(db: TIBDatabase); virtual;
217     procedure DBDisconnect(db: TIBDatabase); virtual;
218     procedure TRStart(tr: TIBTransaction); virtual;
219     procedure TRCommit(tr: TIBTransaction); virtual;
220     procedure TRCommitRetaining(tr: TIBTransaction); virtual;
221     procedure TRRollback(tr: TIBTransaction); virtual;
222     procedure TRRollbackRetaining(tr: TIBTransaction); virtual;
223     procedure ServiceAttach(service: TIBMonitoredService); virtual; overload;
224     procedure ServiceDetach(service: TIBMonitoredService); virtual; overload;
225     procedure ServiceQuery(service: TIBMonitoredService); virtual; overload;
226     procedure ServiceStart(service: TIBMonitoredService); virtual; overload;
227     procedure ServiceAttach(service: TIBXMonitoredConnection); virtual; overload;
228     procedure ServiceDetach(service: TIBXMonitoredConnection); virtual; overload;
229     procedure ServiceQuery(service: TIBXMonitoredService); virtual; overload;
230     procedure ServiceStart(service: TIBXMonitoredService); virtual; overload;
231     procedure SendMisc(Msg : String);
232     function GetEnabled: Boolean;
233     function GetTraceFlags: TTraceFlags;
234     function GetMonitorCount : Integer;
235     procedure SetEnabled(const Value: Boolean);
236     procedure SetTraceFlags(const Value: TTraceFlags);
237     procedure ForceRelease;
238     property TraceFlags: TTraceFlags read GetTraceFlags write SetTraceFlags;
239     property Enabled : Boolean read GetEnabled write SetEnabled default true;
240     end;
241    
242     { TWriterThread }
243    
244     TWriterThread = class(TThread)
245     private
246     { Private declarations }
247     FGlobalInterface: TGlobalInterface;
248     FMsgs : TObjectList;
249     FCriticalSection: TCriticalSection;
250     FMsgAvailable: TEventObject;
251     procedure RemoveFromList;
252     procedure PostRelease;
253     public
254     procedure ReleaseMonitor(Arg : TIBCustomSQLMonitor);
255     protected
256     procedure BeginWrite;
257     procedure EndWrite;
258     procedure Execute; override;
259     procedure WriteToBuffer;
260     public
261     constructor Create(GlobalInterface: TGlobalInterface);
262     destructor Destroy; override;
263     procedure WriteSQLData(Msg : String; DataType : TTraceFlag);
264     end;
265    
266     { TReaderThread }
267    
268     TReaderThread = class(TThread)
269     private
270     { Private declarations }
271     st : TTraceObject;
272     FMonitors : TObjectList;
273     FGlobalInterface: TGlobalInterface;
274     FCriticalSection: TCriticalSection;
275     procedure AlertMonitors;
276     protected
277     procedure BeginRead;
278     procedure EndRead;
279     procedure ReadSQLData;
280     procedure Execute; override;
281     public
282     constructor Create(GlobalInterface: TGlobalInterface);
283     destructor Destroy; override;
284     procedure AddMonitor(Arg : TIBCustomSQLMonitor);
285     procedure RemoveMonitor(Arg : TIBCustomSQLMonitor);
286     end;
287    
288    
289     var
290     FWriterThread : TWriterThread;
291     FReaderThread : TReaderThread;
292     _MonitorHook: TIBSQLMonitorHook;
293     bDone: Boolean;
294     CS : TCriticalSection;
295    
296     const
297     ApplicationTitle: string = 'Unknown';
298    
299     { TIBCustomSQLMonitor }
300    
301     constructor TIBCustomSQLMonitor.Create(AOwner: TComponent);
302     var aParent: TComponent;
303     begin
304     inherited Create(AOwner);
305     FTraceFlags := [tfqPrepare .. tfMisc];
306     if not (csDesigning in ComponentState) then
307     begin
308     aParent := AOwner;
309     while aParent <> nil do
310     begin
311     if aParent is TCustomApplication then
312     begin
313     ApplicationTitle := TCustomApplication(aParent).Title;
314     break;
315     end;
316     aParent := aParent.Owner;
317     end;
318     MonitorHook.RegisterMonitor(self);
319     end;
320     FEnabled := true;
321     end;
322    
323     destructor TIBCustomSQLMonitor.Destroy;
324     begin
325     if not (csDesigning in ComponentState) then
326     begin
327     if FEnabled and assigned(_MonitorHook) then
328     MonitorHook.UnregisterMonitor(self);
329     end;
330     inherited Destroy;
331     end;
332    
333     procedure TIBCustomSQLMonitor.Release;
334     begin
335     MonitorHook.ReleaseMonitor(self);
336     end;
337    
338     procedure TIBCustomSQLMonitor.ReleaseObject;
339     begin
340     Free
341     end;
342    
343     procedure TIBCustomSQLMonitor.ReceiveMessage(Msg: TObject);
344     var
345     st: TTraceObject;
346     begin
347     st := (Msg as TTraceObject);
348     if (Assigned(FOnSQLEvent)) and
349     (st.FDataType in FTraceFlags) then
350     FOnSQLEvent(st.FMsg, st.FTimeStamp);
351     st.Free;
352     end;
353    
354     procedure TIBCustomSQLMonitor.SetEnabled(const Value: Boolean);
355     begin
356     if Value <> FEnabled then
357     begin
358     FEnabled := Value;
359     if not (csDesigning in ComponentState) then
360     if FEnabled then
361     Monitorhook.RegisterMonitor(self)
362     else
363     MonitorHook.UnregisterMonitor(self);
364     end;
365     end;
366    
367     { TIBSQLMonitorHook }
368    
369     constructor TIBSQLMonitorHook.Create;
370     begin
371     inherited Create;
372     FTraceFlags := [tfQPrepare..tfMisc];
373     FEnabled := false;
374     end;
375    
376     destructor TIBSQLMonitorHook.Destroy;
377     begin
378     if assigned(FGlobalInterface) then FGlobalInterface.Free;
379     inherited Destroy;
380     end;
381    
382     procedure TIBSQLMonitorHook.DBConnect(db: TIBDatabase);
383     var
384     st : String;
385     begin
386     if FEnabled then
387     begin
388     if not (tfConnect in FTraceFlags * db.TraceFlags) then
389     Exit;
390     st := db.Name + ': [Connect]'; {do not localize}
391     WriteSQLData(st, tfConnect);
392     end;
393     end;
394    
395     procedure TIBSQLMonitorHook.DBDisconnect(db: TIBDatabase);
396     var
397     st: String;
398     begin
399     if (Self = nil) then exit;
400     if FEnabled then
401     begin
402     if not (tfConnect in FTraceFlags * db.TraceFlags) then
403     Exit;
404     st := db.Name + ': [Disconnect]'; {do not localize}
405     WriteSQLData(st, tfConnect);
406     end;
407     end;
408    
409     function TIBSQLMonitorHook.GetEnabled: Boolean;
410     begin
411     Result := FEnabled;
412     end;
413    
414     function TIBSQLMonitorHook.GetMonitorCount: Integer;
415     begin
416     Result := FGlobalInterface.MonitorCount
417     end;
418    
419     function TIBSQLMonitorHook.GetTraceFlags: TTraceFlags;
420     begin
421     Result := FTraceFlags;
422     end;
423    
424     procedure TIBSQLMonitorHook.RegisterMonitor(SQLMonitor: TIBCustomSQLMonitor);
425     begin
426     {$IFDEF DEBUG}writeln('Register Monitor');{$ENDIF}
427     if not assigned(FGlobalInterface) then
428     FGlobalInterface := TGlobalInterface.Create;
429     if not Assigned(FReaderThread) then
430     FReaderThread := TReaderThread.Create(FGlobalInterface);
431     FReaderThread.AddMonitor(SQLMonitor);
432     end;
433    
434     procedure TIBSQLMonitorHook.ReleaseMonitor(Arg: TIBCustomSQLMonitor);
435     begin
436     FWriterThread.ReleaseMonitor(Arg);
437     end;
438    
439     procedure TIBSQLMonitorHook.SendMisc(Msg: String);
440     begin
441     if FEnabled then
442     begin
443     WriteSQLData(Msg, tfMisc);
444     end;
445     end;
446    
447     procedure TIBSQLMonitorHook.ServiceAttach(service: TIBMonitoredService);
448     var
449     st: String;
450     begin
451     if FEnabled then
452     begin
453     if not (tfService in (FTraceFlags * service.TraceFlags)) then
454     Exit;
455     st := service.Name + ': [Attach]'; {do not localize}
456     WriteSQLData(st, tfService);
457     end;
458     end;
459    
460     procedure TIBSQLMonitorHook.ServiceDetach(service: TIBMonitoredService);
461     var
462     st: String;
463     begin
464     if FEnabled then
465     begin
466     if not (tfService in (FTraceFlags * service.TraceFlags)) then
467     Exit;
468     st := service.Name + ': [Detach]'; {do not localize}
469     WriteSQLData(st, tfService);
470     end;
471     end;
472    
473     procedure TIBSQLMonitorHook.ServiceQuery(service: TIBMonitoredService);
474     var
475     st: String;
476     begin
477     if FEnabled then
478     begin
479     if not (tfService in (FTraceFlags * service.TraceFlags)) then
480     Exit;
481     st := service.Name + ': [Query]'; {do not localize}
482     WriteSQLData(st, tfService);
483     end;
484     end;
485    
486     procedure TIBSQLMonitorHook.ServiceStart(service: TIBMonitoredService);
487     var
488     st: String;
489     begin
490     if FEnabled then
491     begin
492     if not (tfService in (FTraceFlags * service.TraceFlags)) then
493     Exit;
494     st := service.Name + ': [Start]'; {do not localize}
495     WriteSQLData(st, tfService);
496     end;
497     end;
498    
499     procedure TIBSQLMonitorHook.ServiceAttach(service: TIBXMonitoredConnection);
500     var
501     st: String;
502     begin
503     if FEnabled then
504     begin
505     if not (tfService in (FTraceFlags * service.TraceFlags)) then
506     Exit;
507     st := service.Name + ': [Attach]'; {do not localize}
508     WriteSQLData(st, tfService);
509     end;
510     end;
511    
512     procedure TIBSQLMonitorHook.ServiceDetach(service: TIBXMonitoredConnection);
513     var
514     st: String;
515     begin
516     if FEnabled then
517     begin
518     if not (tfService in (FTraceFlags * service.TraceFlags)) then
519     Exit;
520     st := service.Name + ': [Detach]'; {do not localize}
521     WriteSQLData(st, tfService);
522     end;
523     end;
524    
525     procedure TIBSQLMonitorHook.ServiceQuery(service: TIBXMonitoredService);
526     var
527     st: String;
528     begin
529     if FEnabled then
530     begin
531     if not (tfService in (FTraceFlags * service.TraceFlags)) then
532     Exit;
533     st := service.Name + ': [Query]'; {do not localize}
534     WriteSQLData(st, tfService);
535     end;
536     end;
537    
538     procedure TIBSQLMonitorHook.ServiceStart(service: TIBXMonitoredService);
539     var
540     st: String;
541     begin
542     if FEnabled then
543     begin
544     if not (tfService in (FTraceFlags * service.TraceFlags)) then
545     Exit;
546     st := service.Name + ': [Start]'; {do not localize}
547     WriteSQLData(st, tfService);
548     end;
549     end;
550    
551     procedure TIBSQLMonitorHook.SetEnabled(const Value: Boolean);
552     begin
553 tony 263 {$ifdef UNIX}
554 tony 217 if Value and not IsMultiThread then
555 tony 221 IBError(ibxeMultiThreadRequired,['IBSQLMonitor']);
556 tony 263 {$endif}
557 tony 209 if FEnabled <> Value then
558     FEnabled := Value;
559     if (not FEnabled) and (Assigned(FWriterThread)) then
560     begin
561     FWriterThread.Terminate;
562     FWriterThread.WaitFor;
563     FreeAndNil(FWriterThread);
564     end;
565     end;
566    
567     procedure TIBSQLMonitorHook.SetTraceFlags(const Value: TTraceFlags);
568     begin
569     FTraceFlags := Value
570     end;
571    
572     procedure TIBSQLMonitorHook.ForceRelease;
573     begin
574     if Assigned(FReaderThread) then
575     begin
576     FReaderThread.Terminate;
577     if not Assigned(FWriterThread) then
578     FWriterThread := TWriterThread.Create(FGlobalInterface);
579     FWriterThread.WriteSQLData(' ', tfMisc);
580     end;
581     end;
582    
583     procedure TIBSQLMonitorHook.SQLExecute(qry: TIBSQL);
584     var
585     st: String;
586     i: Integer;
587     begin
588     if FEnabled then
589     begin
590     if not ((tfQExecute in (FTraceFlags * qry.Database.TraceFlags)) or
591     (tfStmt in (FTraceFlags * qry.Database.TraceFlags)) ) then
592     Exit;
593     if qry.Owner is TIBCustomDataSet then
594     st := TIBCustomDataSet(qry.Owner).Name
595     else
596     st := qry.Name;
597     st := st + ': [Execute] ' + qry.SQL.Text; {do not localize}
598     if qry.Params.GetCount > 0 then begin
599     for i := 0 to qry.Params.GetCount - 1 do begin
600     st := st + CRLF + ' ' + qry.Params[i].Name + ' = ';
601     try
602     if qry.Params[i].IsNull then
603     st := st + '<NULL>'; {do not localize}
604     st := st + qry.Params[i].AsString;
605     except
606     st := st + '<' + SCantPrintValue + '>';
607     end;
608     end;
609     end;
610     WriteSQLData(st, tfQExecute);
611     end;
612     end;
613    
614     procedure TIBSQLMonitorHook.SQLFetch(qry: TIBSQL);
615     var
616     st: String;
617     begin
618     if FEnabled then
619     begin
620     if not ((tfQFetch in (FTraceFlags * qry.Database.TraceFlags)) or
621     (tfStmt in (FTraceFlags * qry.Database.TraceFlags))) then
622     Exit;
623     if qry.Owner is TIBCustomDataSet then
624     st := TIBCustomDataSet(qry.Owner).Name
625     else
626     st := qry.Name;
627     st := st + ': [Fetch] ' + qry.SQL.Text; {do not localize}
628     if (qry.EOF) then
629     st := st + CRLF + ' ' + SEOFReached;
630     WriteSQLData(st, tfQFetch);
631     end;
632     end;
633    
634     procedure TIBSQLMonitorHook.SQLPrepare(qry: TIBSQL);
635     var
636     st: String;
637     begin
638     if FEnabled then
639     begin
640     if not ((tfQPrepare in (FTraceFlags * qry.Database.TraceFlags)) or
641     (tfStmt in (FTraceFlags * qry.Database.TraceFlags))) then
642     Exit;
643     if qry.Owner is TIBCustomDataSet then
644     st := TIBCustomDataSet(qry.Owner).Name
645     else
646     st := qry.Name;
647     st := st + ': [Prepare] ' + qry.SQL.Text + CRLF; {do not localize}
648     st := st + ' Plan: ' + qry.Plan; {do not localize}
649     WriteSQLData(st, tfQPrepare);
650     end;
651     end;
652    
653     procedure TIBSQLMonitorHook.TRCommit(tr: TIBTransaction);
654     var
655     st: String;
656     begin
657     if FEnabled then
658     begin
659     if Assigned(tr.DefaultDatabase) and
660     (not (tfTransact in (FTraceFlags * tr.DefaultDatabase.TraceFlags))) then
661     Exit;
662     st := tr.Name + ': [Commit (Hard commit)]'; {do not localize}
663     WriteSQLData(st, tfTransact);
664     end;
665     end;
666    
667     procedure TIBSQLMonitorHook.TRCommitRetaining(tr: TIBTransaction);
668     var
669     st: String;
670     begin
671     if FEnabled then
672     begin
673     if Assigned(tr.DefaultDatabase) and
674     (not (tfTransact in (FTraceFlags * tr.DefaultDatabase.TraceFlags))) then
675     Exit;
676     st := tr.Name + ': [Commit retaining (Soft commit)]'; {do not localize}
677     WriteSQLData(st, tfTransact);
678     end;
679     end;
680    
681     procedure TIBSQLMonitorHook.TRRollback(tr: TIBTransaction);
682     var
683     st: String;
684     begin
685     if FEnabled then
686     begin
687     if Assigned(tr.DefaultDatabase) and
688     (not (tfTransact in (FTraceFlags * tr.DefaultDatabase.TraceFlags))) then
689     Exit;
690     st := tr.Name + ': [Rollback]'; {do not localize}
691     WriteSQLData(st, tfTransact);
692     end;
693     end;
694    
695     procedure TIBSQLMonitorHook.TRRollbackRetaining(tr: TIBTransaction);
696     var
697     st: String;
698     begin
699     if FEnabled then
700     begin
701     if Assigned(tr.DefaultDatabase) and
702     (not (tfTransact in (FTraceFlags * tr.DefaultDatabase.TraceFlags))) then
703     Exit;
704     st := tr.Name + ': [Rollback retaining (Soft rollback)]'; {do not localize}
705     WriteSQLData(st, tfTransact);
706     end;
707     end;
708    
709     procedure TIBSQLMonitorHook.TRStart(tr: TIBTransaction);
710     var
711     st: String;
712     begin
713     if FEnabled then
714     begin
715     if Assigned(tr.DefaultDatabase) and
716     (not (tfTransact in (FTraceFlags * tr.DefaultDatabase.TraceFlags))) then
717     Exit;
718     st := tr.Name + ': [Start transaction]'; {do not localize}
719     WriteSQLData(st, tfTransact);
720     end;
721     end;
722    
723     procedure TIBSQLMonitorHook.UnregisterMonitor(SQLMonitor: TIBCustomSQLMonitor);
724     var
725     Created : Boolean;
726     begin
727     {$IFDEF DEBUG}writeln('Unregister Monitor');{$ENDIF}
728     if assigned(FReaderThread) then
729     begin
730     FReaderThread.RemoveMonitor(SQLMonitor);
731     if FReaderThread.FMonitors.Count = 0 then
732     begin
733     FReaderThread.Terminate;
734    
735     { There is a possibility of a reader thread, but no writer one.
736     When in that situation, the reader needs to be released after
737     the terminate is set. To do that, create a Writer thread, send
738     the release code (a string of ' ' and type tfMisc) and then free
739     it up. }
740    
741     Created := false;
742     if not Assigned(FWriterThread) then
743     begin
744     FWriterThread := TWriterThread.Create(FGlobalInterface);
745     Created := true;
746     end;
747     FWriterThread.WriteSQLData(' ', tfMisc);
748     {$IFDEF DEBUG}writeln('Wait for read Terminate');{$ENDIF}
749     FReaderThread.WaitFor;
750     if assigned(FReaderThread.FatalException) then
751     IBError(ibxeThreadFailed,['Reader',Exception(FReaderThread.FatalException).Message]);
752     {$IFDEF DEBUG}writeln('Freeing Reader Thread');{$ENDIF}
753     FreeAndNil(FReaderThread);
754     {$IFDEF DEBUG}writeln('Reader Thread Freed');{$ENDIF}
755     if Created then
756     begin
757     FWriterThread.Terminate;
758     {$IFDEF DEBUG}writeln('Wait for write Terminate');{$ENDIF}
759     FWriterThread.WaitFor;
760     if assigned(FWriterThread.FatalException) then
761     IBError(ibxeThreadFailed,['Writer',Exception(FWriterThread.FatalException).Message]);
762     FreeAndNil(FWriterThread);
763     end;
764     end;
765     end;
766     {$IFDEF DEBUG}writeln('Unregister done'){$ENDIF}
767     end;
768    
769     procedure TIBSQLMonitorHook.WriteSQLData(Text: String;
770     DataType: TTraceFlag);
771     begin
772     {$IFDEF DEBUG}writeln('Write SQL Data: '+Text);{$ENDIF}
773     if not assigned(FGlobalInterface) then
774     FGlobalInterface := TGlobalInterface.Create;
775     Text := CRLF + '[Application: ' + ApplicationTitle + ']' + CRLF + Text; {do not localize}
776     if not Assigned(FWriterThread) then
777     FWriterThread := TWriterThread.Create(FGLobalInterface);
778     FWriterThread.WriteSQLData(Text, DataType);
779     end;
780    
781     { TWriterThread }
782    
783     constructor TWriterThread.Create(GlobalInterface: TGlobalInterface);
784    
785     begin
786     inherited Create(true);
787     {$IFDEF DEBUG}writeln('Write Object Created');{$ENDIF}
788     FGlobalInterface := GlobalInterface;
789     FMsgs := TObjectList.Create(true);
790     FCriticalSection := TCriticalSection.Create;
791     FMsgAvailable := TEventObject.Create(FGlobalInterface.Sa,true,false,cWriteMessageAvailable);
792     Start;
793     end;
794    
795     destructor TWriterThread.Destroy;
796     begin
797     if assigned(FMsgs) then FMsgs.Free;
798     if assigned(FCriticalSection) then FCriticalSection.Free;
799     if assigned(FMsgAvailable) then FMsgAvailable.Free;
800     inherited Destroy;
801     end;
802    
803     procedure TWriterThread.Execute;
804     begin
805     {$IFDEF DEBUG}writeln('Write Thread starts');{$ENDIF}
806     try
807     { Place thread code here }
808     while ((not Terminated) and (not bDone)) or
809     (FMsgs.Count <> 0) do
810     begin
811     FMsgAvailable.WaitFor(cMsgWaitTime);
812     { Any one listening? }
813     if FGlobalInterface.MonitorCount = 0 then
814     begin
815     if FMsgs.Count <> 0 then
816     begin
817     {$IFDEF DEBUG}writeln('Write Thread Drop Message');{$ENDIF}
818     RemoveFromList;
819     end;
820     end
821     else
822     { Anything to process? }
823     if FMsgs.Count <> 0 then
824     begin
825     { If the current queued message is a release release the object }
826     if FMsgs.Items[0] is TReleaseObject then
827     begin
828     {$IFDEF DEBUG}writeln('Post Release');{$ENDIF}
829 tony 215 if not Terminated then
830     Synchronize(PostRelease);
831 tony 209 end
832     else
833     { Otherwise write the TraceObject to the buffer }
834     begin
835     WriteToBuffer;
836     end;
837     end
838     else
839     begin
840     FCriticalSection.Enter;
841     try
842     if FMsgs.Count = 0 then
843     FMsgAvailable.ResetEvent
844     finally
845     FCriticalSection.Leave
846     end;
847     end;
848     end;
849     except on E: Exception do
850     begin
851     {$IFDEF DEBUG}writeln('Write Thread raised Exception: ' + E.Message);{$ENDIF}
852     raise
853     end
854     end;
855     {$IFDEF DEBUG}writeln('Write Thread Ends');{$ENDIF}
856     end;
857    
858     procedure TWriterThread.WriteSQLData(Msg : String; DataType: TTraceFlag);
859     begin
860     FCriticalSection.Enter;
861     try
862     FMsgs.Add(TTraceObject.Create(Msg, DataType));
863     finally
864     FCriticalSection.Leave;
865     end;
866     FMsgAvailable.SetEvent
867     end;
868    
869     procedure TWriterThread.BeginWrite;
870     begin
871     {$IFDEF DEBUG}writeln('Begin Write');{$ENDIF}
872     with FGlobalInterface do
873     begin
874     ReadReadyEvent.PassThroughGate; {Wait for readers to become ready }
875     WriterBusyEvent.Lock; {Set Busy State}
876     end;
877     {$IFDEF DEBUG}writeln('Begin Write Complete');{$ENDIF}
878     end;
879    
880     procedure TWriterThread.EndWrite;
881     begin
882     {$IFDEF DEBUG}writeln('End Write');{$ENDIF}
883     with FGlobalInterface do
884     begin
885     DataAvailableEvent.Unlock; { Signal Data Available. }
886     ReadFinishedEvent.PassThroughGate; {Wait for readers to finish }
887     DataAvailableEvent.Lock; {reset Data Available }
888     WriterBusyEvent.Unlock; {Signal not Busy }
889     end;
890     {$IFDEF DEBUG}writeln('End Write Complete');{$ENDIF}
891     end;
892    
893     procedure TWriterThread.WriteToBuffer;
894     var I, len: integer;
895     Temp: TTraceObject;
896     begin
897     {$IFDEF DEBUG}writeln('Write to Buffer');{$ENDIF}
898     FGlobalInterface.WriteLock.Lock;
899     try
900     { If there are no monitors throw out the message
901     The alternative is to have messages queue up until a
902     monitor is ready.}
903    
904     if FGlobalInterface.MonitorCount = 0 then
905     RemoveFromList
906     else
907     begin
908     i := 1;
909     len := Length(TTraceObject(FMsgs[0]).FMsg);
910     if len <= FGlobalInterface.MaxBufferSize then
911     begin
912     BeginWrite;
913     try
914     FGlobalInterface.SendTrace(TTraceObject(FMsgs[0]))
915     finally
916     RemoveFromList;
917     EndWrite
918     end;
919     end
920     else
921     try
922     while len > 0 do
923     begin
924     {$IFDEF DEBUG}writeln('Sending Partial Message, len = ',len);{$ENDIF}
925     Temp := TTraceObject.Create(TTraceObject(FMsgs[0]),i,Min(len,FGlobalInterface.MaxBufferSize));
926     try
927     BeginWrite;
928     FGlobalInterface.SendTrace(Temp);
929     Inc(i,FGlobalInterface.MaxBufferSize);
930     Dec(len,FGlobalInterface.MaxBufferSize);
931     finally
932     Temp.Free;
933     EndWrite
934     end
935     end;
936     finally
937     RemoveFromList;
938     end
939     end;
940     finally
941     FGlobalInterface.WriteLock.Unlock;
942     end;
943     {$IFDEF DEBUG}writeln('Done Write');{$ENDIF}
944     end;
945    
946     procedure TWriterThread.RemoveFromList;
947     begin
948     {$IFDEF DEBUG}writeln('Write Thread: Remove object From List');{$ENDIF}
949     FCriticalSection.Enter;
950     try
951     FMsgs.Remove(FMsgs[0]); { Pop the written item }
952     finally
953     FCriticalSection.Leave;
954     end;
955     end;
956    
957     procedure TWriterThread.PostRelease;
958     var Monitor: TIBCustomSQLMonitor;
959     begin
960     Monitor := TReleaseObject(FMsgs.Items[0]).FMonitor;
961     Monitor.ReleaseObject
962     end;
963    
964     procedure TWriterThread.ReleaseMonitor(Arg : TIBCustomSQLMonitor);
965     begin
966     FMsgs.Add(TReleaseObject.Create(Arg));
967     end;
968    
969     { TTraceObject }
970    
971     constructor TTraceObject.Create(Msg : String; DataType: TTraceFlag);
972     begin
973     FMsg := Msg;
974     FDataType := DataType;
975     FTimeStamp := Now;
976     end;
977    
978     constructor TTraceObject.Create(obj: TTraceObject);
979     begin
980     FMsg := obj.FMsg;
981     FDataType := obj.FDataType;
982     FTimeStamp := obj.FTimeStamp;
983     end;
984    
985     constructor TTraceObject.Create(obj: TTraceObject; MsgOffset, MsgLen: integer);
986     begin
987     FDataType := obj.FDataType;
988     FTimeStamp := obj.FTimeStamp;
989     FMsg := copy(obj.FMsg,MsgOffset,MsgLen)
990     end;
991    
992     { TReleaseObject }
993    
994     constructor TReleaseObject.Create(Monitor : TIBCustomSQLMonitor);
995     begin
996     FMonitor := Monitor;
997     end;
998    
999     { ReaderThread }
1000    
1001     procedure TReaderThread.AddMonitor(Arg: TIBCustomSQLMonitor);
1002     begin
1003     FCriticalSection.Enter;
1004     try
1005     if FMonitors.IndexOf(Arg) < 0 then
1006     FMonitors.Add(Arg);
1007     finally
1008     FCriticalSection.Leave
1009     end;
1010     end;
1011    
1012     procedure TReaderThread.AlertMonitors;
1013     var i : Integer;
1014     FTemp : TTraceObject;
1015     Monitor: TIBCustomSQLMonitor;
1016     begin
1017     for i := 0 to FMonitors.Count - 1 do
1018     begin
1019     {$IFDEF DEBUG}writeln('Sending Message to Monitor ' +IntToStr(i));{$ENDIF}
1020     FTemp := TTraceObject.Create(st);
1021     Monitor := TIBCustomSQLMonitor(FMonitors[i]);
1022     Monitor.ReceiveMessage(FTemp);
1023     end;
1024     end;
1025    
1026     procedure TReaderThread.BeginRead;
1027     begin
1028     {$IFDEF DEBUG}writeln('Begin Read');{$ENDIF}
1029     with FGlobalInterface do
1030     begin
1031     WriterBusyEvent.PassthroughGate; { Wait for Writer not busy}
1032     ReadFinishedEvent.Lock; { Prepare Read Finished Gate}
1033     ReadReadyEvent.Unlock; { Signal read ready }
1034     {$IFDEF DEBUG}writeln('Read Ready Unlocked');{$ENDIF}
1035     DataAvailableEvent.PassthroughGate; { Wait for a Data Available }
1036     end;
1037     {$IFDEF DEBUG}writeln('Begin Read Complete');{$ENDIF}
1038     end;
1039    
1040     constructor TReaderThread.Create(GlobalInterface: TGlobalInterface);
1041     begin
1042     inherited Create(true);
1043     FGlobalInterface := GlobalInterface;
1044     st := TTraceObject.Create('', tfMisc);
1045     FGlobalInterface.IncMonitorCount;
1046     FMonitors := TObjectList.Create(false);
1047     FCriticalSection := TCriticalSection.Create;
1048     {$IFDEF DEBUG}writeln('Reader Thread Created');{$ENDIF}
1049     FGlobalInterface.ReadReadyEvent.Lock; { Initialise Read Ready}
1050     Start;
1051     end;
1052    
1053     destructor TReaderThread.Destroy;
1054     begin
1055     {$IFDEF DEBUG}writeln('Reader Thread Destory');{$ENDIF}
1056     FGlobalInterface.ReadReadyEvent.UnLock;
1057     if assigned(FGlobalInterface) and (FGlobalInterface.MonitorCount > 0) then
1058     FGlobalInterface.DecMonitorCount;
1059     FMonitors.Free;
1060     if assigned(FCriticalSection) then FCriticalSection.Free;
1061     st.Free;
1062     inherited Destroy;
1063     end;
1064    
1065     procedure TReaderThread.EndRead;
1066     begin
1067     {$IFDEF DEBUG}writeln('End Read');{$ENDIF}
1068     FGlobalInterface.ReadReadyEvent.Lock; { reset Read Ready}
1069     FGlobalInterface.ReadFinishedEvent.Unlock; {Signal Read completed }
1070     {$IFDEF DEBUG}writeln('End Read Complete');{$ENDIF}
1071     end;
1072    
1073     procedure TReaderThread.Execute;
1074     begin
1075     {$IFDEF DEBUG}writeln('Read Thread Starts');{$ENDIF}
1076     { Place thread code here }
1077     while (not Terminated) and (not bDone) do
1078     begin
1079     ReadSQLData;
1080     if (st.FMsg <> '') and
1081     not ((st.FMsg = ' ') and (st.FDataType = tfMisc)) then
1082     begin
1083     {$IFDEF DEBUG}writeln('Sending Message to Monitors');{$ENDIF}
1084 tony 215 if not Terminated then
1085     Synchronize(AlertMonitors);
1086 tony 209 end;
1087     end;
1088     {$IFDEF DEBUG}writeln('Read Thread Ends');{$ENDIF}
1089     end;
1090    
1091     procedure TReaderThread.ReadSQLData;
1092     begin
1093     st.FMsg := '';
1094     BeginRead;
1095     if not bDone then
1096     try
1097     FGlobalInterface.ReceiveTrace(st)
1098     finally
1099     EndRead;
1100     end;
1101     end;
1102    
1103     procedure TReaderThread.RemoveMonitor(Arg: TIBCustomSQLMonitor);
1104     begin
1105     FCriticalSection.Enter;
1106     try
1107     FMonitors.Remove(Arg);
1108     finally
1109     FCriticalSection.Leave
1110     end;
1111     end;
1112    
1113     { Misc methods }
1114    
1115     function MonitorHook: IIBSQLMonitorHook;
1116     begin
1117     if (_MonitorHook = nil) and (not bDone) then
1118     begin
1119     CS.Enter;
1120     if (_MonitorHook = nil) and (not bDone) then
1121     begin
1122     _MonitorHook := TIBSQLMonitorHook.Create;
1123     _MonitorHook._AddRef;
1124     end;
1125     CS.Leave;
1126     end;
1127     result := _MonitorHook;
1128     end;
1129    
1130     procedure EnableMonitoring;
1131     begin
1132     MonitorHook.Enabled := True;
1133     end;
1134    
1135     procedure DisableMonitoring;
1136     begin
1137     MonitorHook.Enabled := False;
1138     end;
1139    
1140     function MonitoringEnabled: Boolean;
1141     begin
1142     result := MonitorHook.Enabled;
1143     end;
1144    
1145     procedure CloseThreads;
1146     begin
1147     {$IFDEF DEBUG}writeln('Closed Threads Called');{$ENDIF}
1148     if Assigned(FReaderThread) then
1149     begin
1150     FReaderThread.Terminate;
1151     FReaderThread.WaitFor;
1152     FreeAndNil(FReaderThread);
1153     end;
1154     if Assigned(FWriterThread) then
1155     begin
1156     FWriterThread.Terminate;
1157     FWriterThread.WaitFor;
1158     FreeAndNil(FWriterThread);
1159     end;
1160     end;
1161    
1162     initialization
1163     CS := TCriticalSection.Create;
1164     _MonitorHook := nil;
1165     FWriterThread := nil;
1166     FReaderThread := nil;
1167     bDone := False;
1168     {$IFDEF USE_SV5_IPC}
1169     if GetEnvironmentVariable('FBSQL_IPCFILENAME') <> '' then
1170     IPCFileName := GetEnvironmentVariable('FBSQL_IPCFILENAME')
1171     else
1172     IPCFileName := GetTempDir(true) + IPCFileName + '.' + GetEnvironmentVariable('USER');
1173     {$ENDIF}
1174    
1175     finalization
1176     {$IFDEF DEBUG}writeln('Entered Finalisation');{$ENDIF}
1177     try
1178     { Write an empty string to force the reader to unlock during termination }
1179     bDone := True;
1180     if Assigned(_MonitorHook) then
1181     _MonitorHook.ForceRelease;
1182     CloseThreads;
1183     if Assigned(_MonitorHook) then
1184     _MonitorHook._Release;
1185    
1186     finally
1187     _MonitorHook := nil;
1188     if assigned(CS) then CS.Free;
1189     end;
1190     end.