Visual Studio 2015

Schnellere Problemdiagnose mit IntelliTrace

Angelos Petropoulos

Denken Sie beim Debuggen an Ihren typischen Workflow. Bis Sie die Ursache eines Problems erfolgreich identifiziert haben, stecken Sie in einer Schleife fest, in der Sie Haltepunkte festlegen und Testschritte wiederholen, die das Problem reproduzieren. Sie können nun IntelliTrace zum Aufzeichnen eines Verlaufs von Debugginginformationen nutzen, während die Anwendung ausgeführt wird. So kommen Sie aus dieser Schleife wieder heraus. Sie können die Testschritte einmal ausführen, um das Problem zu reproduzieren, und dann mithilfe des verlaufsbezogenen Debuggings die Ursache ermitteln.

IntelliTrace ist eine Sammlung von Technologien für das verlaufsbezogene Debugging, die den Debugger in Visual Studio 2015 Enterprise ergänzt. Es gibt auch eine eigenständige Komponente, die Sie außerhalb von Visual Studio verwenden können. IntelliTrace zeichnet die Ausführung Ihrer Anwendung auf der Suche nach interessanten Ereignissen auf. Wenn ein interessantes Ereignis auftritt, zeichnet IntelliTrace automatisch die Aufrufliste und lokalen Variablen auf, während die Anwendung weiter ausgeführt wird. Sie können die Ereignisse steuern, die IntelliTrace als "interessant" einstuft, und zwar über "Extras | Optionen | IntelliTrace | IntelliTrace-Ereignisse".

IntelliTrace bietet Ihnen einen Verlauf der Ausführung Ihrer Anwendung auf einer Zeitachse (einer allgemeinen Übersicht der Ereignisse) und in einer Tabelle, die Details zu den einzelnen Ereignissen enthält. Außerdem erhalten Sie durch eine erweiterte Integration in Visual Studio-Debugger Zugriff auf verlaufsbezogene Debuggingdaten. Dadurch können Sie zeitlich zurückgehen und sich die Aufrufliste und lokalen Variablen der gesammelten Ereignisse ansehen.

IntelliTrace hat in Visual Studio 2015 im Fenster "Diagnosetools" ein neues Zuhause gefunden. Das Fenster "Diagnosetools" enthält neben IntelliTrace die Tools "CPU-Auslastung" und "Speicherauslastung". Falls Ihr Projekttyp samt Debugkonfiguration unterstützt wird (neueste Informationen finden Sie unter aka.ms/diagtoolswindow), wird das Fenster "Diagnosetools" angezeigt, sobald Sie in Visual Studio 2015 mit dem Debuggen beginnen (Sie können F5 drücken oder es stets auch manuell über "Debuggen| Diagnosetools anzeigen" öffnen). Wenn Sie "Verlaufsbezogenes Debugging" aktivieren, sieht die Ansicht wie in Abbildung 1 aus.

Verlaufsbezogenes Debuggen mit IntelliTrace
Abbildung 1: Verlaufsbezogenes Debuggen mit IntelliTrace

Untersuchen der Benutzeroberfläche

Es folgt eine Liste der einzelnen Komponenten auf der Benutzeroberfläche von IntelliTrace und ihrer vorgesehenen Zwecke und Funktionen:

Debuggerereignisse: Die Detailtabelle "Debuggerereignisse" (siehe Abbildung 2) ist eine tabellarische Ansicht der Ereignisse, die von IntelliTrace erfasst wurden. Die Spalten von links nach rechts sind:

  1. Ein Zeiger auf das Ereignis, für das der Debugger derzeit Informationen angezeigt. Nur eine Zeile enthält den gelben Pfeil, der die Position des aktuellen Anweisungszeigers angibt. Eine Zeile enthält möglicherweise einen rosa Pfeil, der angibt, welche Verlaufsereignisse Sie aktiviert haben.
  2. Das Symbol, das dieses Ereignis auf der Zeitachse "Debuggerereignisse" darstellt.
  3. Eine kurze Beschreibung des Ereignisses.
  4. Die Anzahl der Sekunden vom Beginn der Debugsitzung bis zum Zeitpunkt, zu dem das Ereignis erfasst wurde.
  5. Die Dauer des Ereignisses. (Hinweis: Nicht alle Ereignisse haben einen Zeitraum.)
  6. Die Thread-ID und der Name, der das Ereignis generiert hat. (Hinweis: Nicht alle Ereignisse sind einem Thread zugeordnet.)

Tabelle "Debuggerereignisse"
Abbildung 2: Tabelle "Debuggerereignisse"

Wenn Sie auf ein Ereignis in der Liste klicken, um es zu erweitern, können Sie "Verlaufsbezogenes Debugging" aktivieren und den Debugger auf die Stelle festlegen, an der das ausgewählte Ereignis von IntelliTrace aufgezeichnet wurde.

Steuerelement "Kategoriefilter": Dieses Filtersteuerelement dient zum Aus- oder Einblenden von Ereigniskategorien, während ihre Erfassung läuft. Wenn Sie sich auf eine bestimmte Kategorie konzentrieren möchten oder an einer anderen überhaupt nicht interessiert sind, können Sie diese hiermit schnell ein- und ausblenden. Die aktuelle Liste der Kategorien umfasst die folgenden: ADO.NET, ASP.NET, Konsole, Datenbindung, Debugger, Umgebungsvariablen, Ausnahme, Datei, Geste, Verzögerte Initialisierung, Ausgabe, Registrierung, Dienstmodell, Threading, Ablaufverfolgung, Benutzereingabeaufforderung und XAML.

Steuerelement "Threadfilter": Dieses Filtersteuerelement dient zum Aus- oder Einblenden von Ereignissen nach den Thread, in dem sie generiert wurden, wenn Sie sich nur für die Diagnose eines bestimmten Threads interessieren oder sicher sind, dass das Ausführen eines bestimmten Threads keine Probleme aufweist.

Schaltfläche "Ereignisse aus externem Code anzeigen": IntelliTrace beachtet die Debuggereinstellung "Nur eigenen Code". Dies bedeutet, dass standardmäßig Ereignisse ausgeblendet werden, die zu nicht vom Benutzer stammenden Code gehören, um für Übersichtlichkeit zu sorgen. Durch Klicken auf diese Schaltfläche wird die Einstellung des Debuggers umgangen, und Ereignisse aus externem Code werden angezeigt. In den meisten Fällen führt dies zu einer ausführlichen Ausgabe.

Zeitachse für Debuggerereignisse: Dies ist eine grafische Ansicht der Ereignisse, die von IntelliTrace im Zeitablauf gesammelt wurden. Dies ist eine andere Ansicht der Informationen, die in der Tabelle "Ereignisdetails" enthalten sind. Die Zeitachse bietet eine allgemeine Ansicht, in der Sie Bereiche bestimmen und auswählen können, die Sie in der Tabellenansicht "Ereignisdetails" analysieren möchten. Sie können die Anzeige in der Tabellenansicht "Ereignisdetails" filtern, indem Sie einen bestimmten Zeitbereich auswählen.

Das Lineal: Über der Zeitachse ist ein Lineal, das den Zeitpunkt jedes einzelnen eingetretenen Ereignisses zeigt. Durch Klicken und Ziehen können Sie auch einen bestimmten Zeitbereich auswählen. Wählen Sie einen Zeitraum aus, um die Detailtabelle "Debuggerereignisse" zu filtern.

Spur "Unterbrechungsbezogene Ereignisse": Jedes Mal, wenn ein unterbrechnungsbezogenes Ereignis auftritt, wird es auf dieser Zeitachsenspur angezeigt. Unterbrechungsbezogene Ereignisse sind das Treffen auf Haltepunkte, ausgeführte Schritte, das Klicken auf "Alle unterbrechen", das Aufrufen von "Debugger.Break" oder eine nicht behandelte Ausnahme, die die Ausführung unterbricht. Stellen Sie sich dies als die Hauptspur der Zeitachse vor, um sich besser zu orientieren, wo bei der Ausführung des Programms Ereignisse in den anderen Spuren aufgetreten sind (denn mithilfe von Haltepunkten und Schritten steuern Sie die Ausführung Ihrer Anwendung). Durch Klicken auf ein Ereignis in dieser Spur wird ein Zeitfilter angewendet, mit dem Ereignisse in der Detailtabelle "Debuggerereignisse" gefiltert werden. Auf diese Weise können Sie einfach nur die Ereignisse filtern, die aufgetreten sind, nachdem Sie einen Prozedurschritt auf eine Codezeile angewendet oder F5 gedrückt haben und auf einen Haltepunkt getroffen sind.

Spur "Ausgabeereignisse": Diese Spur zeigt Ereignisse für Meldungen, die im Ausgabefenster angezeigt werden. Auf dieser Spur werden die folgenden Ereigniskategorien angezeigt: Ausgelöste Ausnahmen, Programmausgabe (oder "Console.WriteLine"), Modul geladen/entladen, Ende des Threads und Ende des Prozesses. Dadurch können Sie die standardmäßigen Debugausgabemeldungen mit dem Rest der Verlaufsinformationen des Debuggers korrelieren.

Spur "IntelliTrace-Ereignis": Jede andere von IntelliTrace erfasste Ereigniskategorie wird auf dieser Zeitachsenspur angezeigt: ADO.NET, ASP.NET, Konsole, Datenbindung, Umgebungsvariablen, Datei, Geste, Verzögerte Initialisierung, Registrierung, Dienstmodell, Threading, Ablaufverfolgung, Benutzereingabeaufforderung und XML.

Symbolleiste "Diagnosetools": Die Symbolleiste bietet die Schaltflächen "Vergrößern" und "Verkleinern" sowie "Ansicht zurücksetzen", um die Zeitachse auf den standardmäßigen Zoomfaktor zurückzusetzen und die vorhandene Zeitauswahl aufzuheben. Hiermit werden alle erfassten Daten zur Ansicht gefiltert. Die Dropdownliste "Tools auswählen" ermöglicht das Auswählen der Tools, die im Fenster "Diagnosetools" neben IntelliTrace enthalten sein sollen.

Sie können mehrere Diagnosetools gleichzeitig ausführen. Das Fenster "Diagnosetools" kann die Tools "Speicherauslastung", "CPU-Auslastung" und IntelliTrace gleichzeitig enthalten. Dadurch erhalten Sie eine ganzheitliche Sicht auf das Verhalten Ihrer Anwendung. Abbildung 3 zeigt beispielsweise, wie die Reihe von Ereignissen vom Typ "Modul laden" die Speicher- und CPU-Auslastung einer ASP.NET-Anwendung ansteigen lässt, sobald sie gestartet wird.

Das Fenster "Diagnosetools" veranschaulicht das Verhalten und die Leistung Ihrer App
Abbildung 3: Das Fenster "Diagnosetools" veranschaulicht das Verhalten und die Leistung Ihrer App

Beheben eines echten Fehlers mit IntelliTrace

Nun werde ich Sie schrittweise durch das Beheben eines echten Fehlers mithilfe von Livedebuggingfeatures von IntelliTrace in Visual Studio 2015 Enterprise begleiten. Die Anwendung, die ich debuggen werde, ist eine Windows Forms-Anwendung aus CodeProject mit dem Namen SocialClub.

Die App dient zum Verwalten der Mitgliedsdatenbank eines Freizeitklubs. Der Fehler ist, dass sich die Suchfunktion nach der Registrierung eines Mitglieds unberechenbar verhält. Zum Reproduzieren des Fehlers starte ich die Anwendung und registriere ein neues Mitglied. Dann führe ich den Suchvorgang "Alle abrufen" durch, der alle registrierten Mitglieder zurückgeben sollte. Ich erwarte nur ein Ergebnis, aber stattdessen erhalte ich zwei (siehe Abbildung 4). Das zweite Suchergebnis ist unerwartet, weshalb ich die Ursache dafür beseitigen muss.

Das Abrufen aller Suchergebnisse gibt einen unerwarteten Datensatz zurück
Abbildung 4: Das Abrufen aller Suchergebnisse gibt einen unerwarteten Datensatz zurück

Was muss ich also tun, um diesen Fehler zu beheben? An diesem Punkt ist meine Hypothese, dass entweder ein Problem mit der Suchfunktion "Alle abrufen" oder mit dem Registrierungsprozess für neue Mitglieder vorliegt. Die Anwendung hat einen anderen Suchmodus, der bestimmte Suchkriterien akzeptiert. Deshalb nutze ich diesen, um den unerwarteten Datensatz zu suchen, der von "Alle abrufen" zurückgegeben wurde (denjenigen mit den fehlenden Daten und unbekannten Werten).

Es folgen die möglichen Szenarien: Wenn keine Ergebnisse angezeigt werden, bedeutet dies wahrscheinlich, dass der unerwartete Datensatz nicht in der Datenbank vorhanden ist und dass das Problem bei der Suchfunktion "Alle abrufen" liegt. Wenn ich eine Ergebnisübereinstimmung mit einem Datensatz mit "Unknown" für "Occupation" und "MaritalStatus" erhalte, liegt das Problem wahrscheinlich bei der Registrierungsfunktion, die mehr Datensätze in die Datenbank eingibt, als sie sollte.

Nun führe ich eine Suche mit "Unknown" für "Occupation" und "Unknown" für "MaritalStatus" durch, die exakt ein Ergebnis zurückgibt, nämlich den Datensatz, die ich erfolgreich mit "Engineer" und "Married" registriert habe. Das ist seltsam und bringt mich leider der Ursache des Fehlers nicht näher. Anstatt Zeit damit zu verbringen, Haltepunkte festzulegen, neue Mitglieder zu registrieren und immer wieder nach ihnen suchen, will ich prüfen, wie IntelliTrace die Untersuchung beschleunigen kann.

Ich möchte die Ereignisse anzeigen, die von IntelliTrace erfasst wurden. Doch IntelliTrace-Ereignisse werden erst aktualisiert, nachdem der Debugger die Ausführung der Anwendung unterbrochen hat (sobald ein Haltepunkt erreicht wird). Da es keinen bestimmten Haltepunkt gibt, der mich interessiert, klicken ich einfach auf der Symbolleiste von Visual Studio auf "Alle unterbrechen". Die App ist jetzt im Zustand "Unterbrochen", da alle Threads angehalten wurden. IntelliTrace zeigt die gesammelten Daten im Fenster "Diagnosetools" sowohl auf der Zeitachse als auch in der tabellarischen Detailansicht an.

An dieser Stelle habe ich mit der Anwendung etwas interagiert, seit ich das Debuggen gestartet habe. Ich habe mich angemeldet, ein neues Mitglied registriert und mit dem Befehl "Alle abrufen" und bestimmten Suchkriterien gesucht. Ich bin jedoch nur an Ereignissen interessiert, die nach dem Klicken auf "Register" aufgetreten sind. Zum Filtern meiner Ansicht auf nur diese Ereignisse bewege ich den Mauszeiger über den Ereignissen auf der Zeitachse, bis ich die Stelle finde, an der ich auf "Register" geklickt habe. Dann ziehe und wähle ich eine Gruppe von Ereignissen aus. Wenn ich meine tabellarische Detailansicht betrachte, nachdem ich eine Zeit ausgewählt habe, sehe ich, dass die beiden letzten aufgeführten Ereignisse (außer "Alle unterbrechen") zwei INSERT-Anweisungen sind (siehe Abbildung 5).

Die tabellarische Detailansicht wurde gefiltert, um Ereignisse im ausgewählten Zeitraum anzuzeigen
Abbildung 5: Die tabellarische Detailansicht wurde gefiltert, um Ereignisse im ausgewählten Zeitraum anzuzeigen

Durch Klicken auf ein Ereignis in der Liste werden mehrere Zeilen angezeigt, die die gesamte SQL-Anweisung enthalten. Ich kann erkennen, dass ich zwei INSERT-Anweisungen durchgeführt habe. Bei der zweiten wird ein fehlerhafter Eintrag mit NULL-Werten eingefügt. Hier sind die beiden SQL-Anweisungen:

Execute Reader "insert [dbo].[ClubMembers]([Name], [DateOfBirth],
  [Occupation], [Salary], [MaritalStatus], [HealthStatus], [NumberOfChildren],
  [ExpirationDate])values (@0, @1, @2, @3, @4, @5, @6, @7)
  select [Id] from [dbo].[ClubMembers] where @@ROWCOUNT > 0 and [Id] =
  scope_identity()"
Execute Reader "insert [dbo].[ClubMembers]([Name], [DateOfBirth],
  [Occupation], [Salary], [MaritalStatus], [HealthStatus], [NumberOfChildren],
  [ExpirationDate])values (null, @0, @1, null, @2, @3, null, @4) select [Id] from
  [dbo].[ClubMembers] where @@ROWCOUNT > 0 and [Id] = scope_identity()"

Sie können die SELECT-Anweisung ignorieren, die auf die INSERT-Anweisung folgt. Sie gehört zu Entity Framework, das die ID des Datensatzes abruft, den sie gerade eingefügt haben. Dies wirft die folgende Frage auf: Warum werden für ein einfaches Klicken auf die Schaltfläche "Register" zwei SQL-Anweisungen ausgeführt? IntelliTrace hilft mir, schnell eine Antwort auf diese Frage zu finden, indem ich "Verlaufsbezogenes Debugging" für die einzelnen Ereignisse aktiviere (siehe Abbildung 6) und im Fenster "Anrufliste" ihren entsprechenden Anruflistenverlauf prüfe.

Aktivieren von "Verlaufsbezogenes Debugging" für die ersten beiden INSERT-Anweisungen
Abbildung 6: Aktivieren von "Verlaufsbezogenes Debugging" für die ersten beiden INSERT-Anweisungen

Der Verlauf der Aufrufliste der ersten INSERT-Anweisung ist:

John.SocialClub.Data.dll!John.SocialClub.Data.Service.ClubMemberService.Create(...)
John.SocialClub.Desktop.exe!John.SocialClub.Desktop.Forms.Membership.Manage.RegisterMember()
John.SocialClub.Desktop.exe!John.SocialClub.Desktop.Forms.Membership.Manage.Register_Click(...)
John.SocialClub.Desktop.exe!John.SocialClub.Desktop.Program.Main()

Der Verlauf der Aufrufliste der zweiten INSERT-Anweisung mit dem fehlerhaften Datensatz ist:

John.SocialClub.Data.dll!John.SocialClub.Data.Service.ClubMemberService.Create(...)
John.SocialClub.Desktop.exe!John.SocialClub.Desktop.Forms.Membership.Manage.RegisterMember()
John.SocialClub.Desktop.exe!John.SocialClub.Desktop.Forms.Membership.Manage.btnRegister_MouseClick(...)
John.SocialClub.Desktop.exe!John.SocialClub.Desktop.Program.Main()

Durch Klicken auf die einzelnen Frames gelange ich zur entsprechenden Codezeile. Bei der Prüfung des Verlaufs der beiden Aufruflisten stelle ich fest, dass ich zwei verschiedenen Ereignishandler demselben Schaltflächenklick zugeordnet habe: "Register_Click(...)" und "btnRegister_MouseClick(...)". Beim Lesen des Codes in diesen beiden Funktionen leite ich schnell her, dass weil die Felder des Formulars nach jeder Registrierung eines neuen Mitglieds zurückgesetzt werden, der erste Ereignishandler die Datensätze ordnungsgemäß in die Datenbank einfügt. Der zweite Ereignishandler fügt jedoch einen Datensatz mit leeren und NULL-Feldern ein. Ich habe den Fehler schnell gefunden, indem ich "Alle unterbrechen" und dann IntelliTrace verwendet habe, um den problematischen Codeabschnitt zu bestimmen und anzusteuern.

Was geschieht, wenn IntelliTrace-Ereignisse nicht ausreichen, um den Fehler zu finden?

An diesem Punkt (wie erfreut auch immer Sie darüber sind, wie IntelliTrace das Debuggen verbessert) werden Sie sich vielleicht fragen, was zu tun ist, wenn IntelliTrace keine interessanten Ereignisse aufgezeichnet, die Sie zur Ursache eines Fehlers führen können. Haben Sie dann einfach Pech? Nein, seien Sie ganz beruhigt. Vergessen Sie nicht, dass Sie steuern können, welche IntelliTrace-Ereignisse aktiviert sind, und zwar über "Extras | Optionen | IntelliTrace | IntelliTrace-Ereignisse". Nicht alle Einstellungen sind standardmäßig aktiviert, doch selbst die Aktivierung aller reicht u. U. nicht immer für einige lästige Fehler aus.

Für diese schwierigen Probleme können Sie IntelliTrace zum Aufzeichnen nicht bloß von Ereignissen, sondern auch aller Methodenaufrufe und ihrer Parameter konfigurieren. Wechseln Sie einfach zu "Extras | Optionen | IntelliTrace", und wählen Sie "IntelliTrace-Ereignisse und -Aufrufinformationen" aus. Dies ist eine leistungsfähige Debugfunktion, die aber auf Kosten der Laufzeit geht. Bei dieser Einstellung fängt IntelliTrace alle Methodenaufrufe ab und zeichnet sie auf, was sich auf die Leistung der App auswirkt. Deshalb werden Methodenaufrufe eben nicht standardmäßig erfasst. Sie müssen dies in den IntelliTrace-Einstellungen aktivieren.

Sie können diese neuen Informationen auf zwei verschiedene Arten anzeigen und ansteuern. Sie können die Unterregisterkarte "Aufrufe" in der Detailtabelle "Debuggerereignisse" verwenden, auf der alle aufgezeichneten Aufrufe aufgeführt sind (weitere Informationen zur Ansicht "Aufrufe" finden Sie unter aka.ms/itracecalls). Eine andere Möglichkeit ist das Aktivieren von "Verlaufsbezogenes Debugging" für ein Ereignis und Verwenden des IntelliTrace-Steuerelements im Text-Editor, um in der Ausführung der Anwendung hin- und her zu navigieren. Die Steuerelemente werden zwischen Ihrem Code und dem Anweisungszeiger angezeigt. IntelliTrace kann Ihnen also in allen Ihren Livedebugszenarien zur Seite stehen.

Was geschieht, wenn Sie den Fehler nicht auf einem Entwicklungscomputer reproduzieren können?

Hier kommt dann das Nicht-Livedebugging mit IntelliTrace ins Spiel. Bisher habe ich vorausgesetzt, dass Sie die erforderlichen Schritte zum Reproduzieren des Problems kennen, das Sie debuggen. Das ist aber nicht immer der Fall. Einige der schwierigsten und zeitaufwendigsten Probleme sind diejenigen, für die Sie die genauen Schritte zum Reproduzieren möglicherweise nicht kennen. Dank IntelliTrace kommt dieses befürchtete Szenario der Unmöglichkeit der Reproduktion nicht zum Tragen, da Sie die Ausführung der Anwendung in einer Produktions- oder Testumgebung aufzeichnen können. Anschließend können Sie sie auf Ihrem Entwicklungscomputer debuggen, indem Sie die gesammelte Informationen im selben Fenster "Diagnosetools" untersuchen, das ich hier verwendet habe.

IntelliTrace bietet einen eigenständigen Datensammler, den Sie in anderen Umgebungen bereitstellen können, mit denen Visual Studio keine Verbindung herstellen kann. Sie sollten keine Widerstände von Ihrer Administratoren erfahren, da keine Installation erforderlich ist. Der Datensammler muss lediglich in die Zielumgebung kopiert werden. Der Datensammler zeichnet die Ausführung der Anwendung in einer ITRACE-Datei auf, die Sie auf ihren Entwicklungscomputer übertragen und mit Visual Studio öffnen können. Dieses Szenario wird als Nicht-Livedebugging bezeichnet, da Sie die Ausführung der App während des Debuggens nicht steuern können. Aktuelle Informationen zur Verwendung des eigenständigen IntelliTrace-Datensammlers finden Sie unter aka.ms/itracecollector.

Zusammenfassung

Die neuen IntelliTrace-Umgebung und Integration in das Fenster "Diagnosetools" bieten einige interessante Möglichkeiten. Unter aka.ms/DiagnosticsBlog finden Sie die neuesten Informationen zu diesen und anderen diagnosebezogenen Features.


Angelos Petropoulos ist ein leitender Programmmanager im Visual Studio-Team. Nach dem Erlangen des Masters in der Entwicklung objektorientierter Software arbeitete er in Großbritannien als IT-Berater. Nach seinem Umzug in die USA wechselte er ins Team für Diagnosetools in Visual Studio, in dem er heute der Programmmanager für IntelliTrace ist.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Andrew Hall, Daniel Motte, Dan Taylor, Charles Willis