Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Diagnostica e strategie di monitoraggio

Link copiato negli appunti

Sebbene un'applicazione Windows Store sia eseguita in un'ambiente sand-boxed "ad alte prestazioni", è possibile che alcuni fenomeni come codice inefficiente, chiamate a servizi remoti o a librerie di terze parti che richiedono troppo tempo, calcoli complessi che impegnano a fondo la CPU, possano determinare problemi di performance tali da pregiudicare la user experience.

Il profiling di un'applicazione Windows Store

Visual Studio 2013 mette a disposizione una serie di strumenti per il profiling di un'applicazione Windows Store che consentono di analizzare e registrare metriche durante l'esecuzione dell'applicazione, raccogliere dati dalla CPU a intervalli regolari, esaminare l'efficienza energetica dell'applicazione e analizzare la fluidità dell'interfaccia utente.

Qualora si rendesse necessario raccogliere informazioni addizionali sulla performance di un'applicazione, è possibile ricorrere a toolkit esterni a Visual Studio, come il Windows Performance Toolkit (WPT), oggi parte del più ampio Windows Assessment and Deployment Kit. Questo tool contiene strumenti di analisi che consentono di raccogliere dati approfonditi sul sistema operativo e sulle applicazioni Windows. Se invece volete analizzare nel dettaglio l'uso della memoria da parte di un'app Windows Store, potete ricorrere al Microsoft NP .NET Profiler Tool. Tenete tuttavia presente che la discussione di questi strumenti, così come di ogni altro tool di terze parti, esula dagli obiettivi di questa guida.

Per vedere come funzionano gli strumenti di profiling disponibili in Visual Studio, creiamo un'applicazione di prova il cui unico obiettivo è quello di recuperare un elenco di nominativi da un servizio remoto (fittizio) e mostrarli a video.

Qui di seguito è riportato il codice HTML che potete usare come riferimento per la pagina di default della vostra app:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo.Html.it.ProfilingSample.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- Demo.Html.it.ProfilingSample.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <button id="btnGetCustomers">Get Customers</button>
    <div id="customerList"></div>
</body>
</html>

Nell'handler dell'evento di click, il codice recupera l'elenco dei clienti tramite una chiamata a un componente Windows Runtime sviluppato in C# e quindi li mostra a video:

app.onloaded = function (args) {
    btnGetCustomers.addEventListener("click", getCustomers_click);
}
function getCustomers_click(args) {
    var biz = new MyWinMDLibrary.FakeBiz();
    var customerList = biz.getCustomers();
    var count = customerList.length;
    for (var i = 0; i < count; i++) {
        displayCustomer(customerList[i]);
    }
}
function displayCustomer(customer) {
    customerList.innerHTML += "<p>" + customer.name + "</p>";
}

La classe FakeBiz rappresenta un componente di business (ovviamente fittizio) esposto tramite una libreria WinMD, come probabilmente accadrebbe in un'app reale (in modo da poter sfruttare i vantaggi offerti da un linguaggio strongly-typed e pienamente object-oriented come C#, soprattutto quando le logiche di business sono complesse).

Questa classe contiene un solo metodo, GetCustomers, che recupera l'elenco dei nominativi simulando una chiamata a un servizio remoto. Il metodo SimulateRemoteServiceCall contiene un loop vuoto che sospende l'esecuzione dell'operazione per alcuni secondi.

Il codice utilizzato per questo esempio ha finalità puramente illustrative e non dovrebbe mai essere usato in un'app reale, poiché congela la user interface fino alla conclusione dell'operazione; meglio utilizzare i pattern asincroni in JavaScript.

Ecco invece il codice per la nostra libreria:

namespace MyWinMDLibrary
{
    public sealed class FakeBiz
    {
        public IList<Person> GetCustomers()
        {
            return this.SimulateRemoteServiceCall();
        }
        private List<Person> SimulateRemoteServiceCall()
        {
            for (int i = 0; i < Int32.MaxValue; i++)
            {
                // code omitted
            }
            return new List<Person>()
            {
                new Person() { Name = "Roberto Brunetti" },
                new Person() { Name = "Vanni Boncinelli" },
                new Person() { Name = "Luca Regnicoli" },
                new Person() { Name = "Katia Egiziano" },
                new Person() { Name = "Paolo Pialorsi" },
                new Person() { Name = "Marco Russo" },
            };
        }
    }
    public sealed class Person
    {
        public String Name { get; set; }
    }
}

Per analizzare questo codice in Visual Studio, occorre cliccare su Debug > Start Performance Analysis:

A questo punto apparirà una nuova finestra in cui selezionare il tipo di profiling da eseguire. Cinque sono le opzioni possibili (gli strumenti CPU Sampling ed Energy Consumption sono stati introdotti solo con Visual Studio 2013).

Opzione Descrizione
CPU Sampling permette di raccogliere informazioni dalla CPU a intervalli regolari allo scopo di determinare quali metodi, all'interno del codice nativo e di quello managed, consumano più tempo CPU.
Energy Consumption consente di analizzare la quantità di energia consumata dall'applicazione, e dove avviene il maggiore consumo (CPU, rete, ecc.)
JavaScript Function Timing esamina il tempo CPU impiegato per eseguire funzioni JavaScript
JavaScript Memory permette di analizzare l'heap e investigare eventuali casi di memory leak
HTML UI Responsiveness raccoglie dati attinenti alla responsività, ossia al grado di fluidità, dell'interfaccia utente di un'applicazione

Vediamo adesso questi strumenti più nel dettaglio, utilizzando la nostra app come "cavia".

CPU Sampling

Questo strumento consente di determinare, mediante snapshot della call stack eseguiti a intervalli regolari (sampling), il consumo di CPU da parte di codice nativo e managed, ad esclusione dunque delle funzioni JavaScript (per il quale è previsto l'apposito tool JavaScript Function Timing, descritto nel prossimo paragrafo). Si tratta dunque di uno strumento particolarmente utile per profilare componenti WinMD sviluppati in linguaggi managed, come vedremo fra un momento (per il profiling di applicazioni Windows Store in XAML/C#, si rinvia all'apposito articolo).

Selezionando questa opzione e premendo su Start, Visual Studio lancerà l'applicazione target e comincerà a registrare i dati provenienti dalla call stack. È possibile in qualunque momento interrompere l'attività di profiling tramite l'apposito pulsante di Stop. A questo punto, Visual Studio 2013 visualizzerà i dati raccolti tramite un Sample Profiling Report, mostrato nella prossima immagine:

La parte superiore del report mostra un grafico in cui viene analizzato il consumo della CPU nel tempo (in secondi). È possibile ingrandire o isolare aree specifiche del grafico, ad esempio un picco di consumo della CPU. Subito sotto, la sezione Hot Path indica gli le chiamate più onerose in termini di utilizzo di CPU. Ciascun metodo è evidenziato con un'icona a forma di fiamma, proprio accanto al nome del metodo. Ciascuna funzione mostra due indicatori, denominati rispettivamente "Inclusive Samples" ed "Exclusive Samples". Il primo valore, detto anche "tempo inclusivo" (inclusive time), indica il tempo CPU impiegato per completare quella particolare funzione, incluso il tempo speso attendendo il completamento degli altri metodi invocati dal metodo stesso. Il secondo valore, o "tempo esclusivo" (exclusive time), è invece da intendersi "al netto" del tempo speso da una funzione per attendere il completamento di altre funzioni, e indica unicamente la quantità di tempo consumato per eseguire il codice interno al metodo stesso (escluse le chiamate a funzioni ulteriori).

Nel nostro esempio, tutti i metodi inclusi nell'Hot Path mostrano un tempo inclusivo superiore al 96%, mentre il tempo esclusivo è inferiore all'1%, con la sola eccezione del metodo SimulateRemoteServiceCall, che riporta un valore del 96,82% per entrambe le voci. Questo significa che praticamente tutto il tempo CPU consumato dall'app è stato usato da questo metodo, e che tutte le altre funzioni non hanno fatto altro che attendere il completamento di questa chiamata.

La sezione Functions Doing Most Individual Work mostra invece il tempo CPU esclusivo consumato da ciascuna delle funzioni chiamate durante l'esecuzione dell'applicazione. Trattandosi di tempo esclusivo, si intende "al netto" del tempo passato ad aspettare il completamento di altri metodi.

Per avere maggiori informazioni su uno dei metodi elencati, è sufficiente cliccare sul nome del metodo, e Visual Studio passerà a una nuova vista di dettaglio, denominata Function Details, mostrata nella prossima immagine.

La parte superiore della vista mostra il tempo inclusivo allocato lungo l'execution path che include la funzione corrente. Le tre colonne mostrano le relazioni tra la funzione corrente (SimulateRemoteServiceCall), rappresentata dalla colonna centrale, la funzione chiamante (GetCustomers), indicata nella colonna di sinistra, e una qualsiasi funzione chiamata a sua volta dal metodo corrente(in questo caso, nessuna), rappresentata dalla terza colonna. Nell'esempio, la colonna Calling Function mostra chiaramente come il 96,8% del tempo CPU è stato speso nell'esecuzione del metodo SimulateRemoteServiceCall.

Particolarmente interessante è il pannello Function Code View nella parte inferiore della vista. Questa vista evidenzia in rosso i punti del codice che causano il collo di bottiglia (nel nostro esempio, il ciclo for all'interno del metodo SimulateRemoteServiceCall), indicando per ciascuna di queste il tempo CPU speso.

Un'altra vista particolarmente utile, soprattutto se per la nostra app abbiamo adottato una soluzione multitier o utilizziamo librerie di terze parti (o, come in questo caso, un componente WinMD), è la vista Modules Vew, la quale mostra i dati raccolti raggruppati per moduli. La prossima immagine ne mostra un esempio.

JavaScript Function Timing

A differenza del tool precedente, che fotografa a intervalli regolari la call stack, questo strumento utilizza tecniche di instrumentation (basate sull'iniezione di codice all'inizio e alla fine dei metodi monitorati) per raccogliere informazioni dettagliate sul timing delle funzioni JavaScript invocate.

La prossima immagine mostra un esempio di report:

Come si vede, la vista ricorda molto quella del CPU Sampling, con una sezione Hot Path che evidenzia l'execution path e, per ciascuna delle funzioni JavaScript incluse, il tempo CPU consumato (sia inclusivo che esclusivo). Nella parte inferiore della vista sono invece evidenziate le funzioni JavaScript con i valori di tempo esclusivo più elevati, in modo da individuare rapidamente i metodi che richiedono più tempo per essere eseguiti (in questo caso, la funzione getCustomers).

Cliccando sul nome di una delle funzioni, è possibile accedere a informazioni più dettagliate, con le stesse colonne mostrano le relazioni tra le chiamate lungo l'execution path viste a proposito del CPU Sampling (ma relative, in questo caso, alle funzioni JavaScript). Anche in questo caso, la parte inferiore dello schermo mostra il codice dei metodi coinvolti. La prossima immagine ne mostra un esempio:

Energy consumption

Lo strumento di analisi "Energy consumption" permette di stimare in modo preciso quanta energia la nostra app consuma, e per quali attività questa energia viene impiegata. La prossima immagine mostra un esempio di report sul consumo energetico della nostra app:

Gli elementi informativi principali sono tre:

  • Nella sezione Estimated Power Usage, il grafico mostra il consumo di energia legato alla CPU, al display e alla rete. Questo grafico è particolarmente utile, poiché permette di evidenziare inaspettati picchi nel consumo di energia in uno di questi tre comparti.
  • La sezione Resources (On/Off) specifica lo stato delle risorse di rete. Cliccando su una delle voci, si ottengono i dettagli relativi al trasferimento dati per ciascuna risorsa.
  • Infine, nell'Estimated Energy Consumption Summary un grafico sintetizza il consumo di energia dell'app e viene indicata la durata prevista della batteria nel caso di uso continuativo dell'applicazione (poco più di 6 ore, nel nostro esempio).

JavaScript Memory

Introdotto inizialmente con Visual Studio 2012 Update 1, lo strumento di profiling JavaScript Memory permette di raccogliere informazioni sull'utilizzo della memoria, come mostrato nella seguente immagine:

Il grafico in alto mostra l'andamento del consumo della memoria in tempo reale. In un qualunque momento è possibile scattare uno "snapshot" dell'heap, ossia dello stato corrente della memoria. Ciascuno snapshot comparirà in un apposito riquadro collocato nella parte inferiore della vista, assieme alle dimensioni dell'heap al momento dello scatto, al numero di oggetti creati dall'applicazione (le frecce indicano l'andamento dei due valori rispetto allo snapshot immediatamente precedente) e all'immagine della user interface in quel momento. La prossima immagine mostra il dettaglio di due snapshot presi prima e dopo aver recuperato la lista dei nomi.

Cliccando sul numero di oggetti nell'angolo superiore destro dello snapshot, è possibile accedere alle informazioni di dettaglio relative agli oggetti creati dall'applicazione, raggruppati per tipo di oggetto. Cliccando invece sulla memoria allocata (in alto a sinistra), si accede all'elenco di oggetti sull'heap che mantengono una reference ad altri oggetti; questo elenco è ordinato per "retained size", ossia la somma delle dimensioni di un oggetto più le dimensioni di tutti i suoi elementi children. In questo modo, gli oggetti che consumano la maggior quantità di memoria sono elencati per primi, come mostrato nella prossima immagine:

UI Responsiveness Profiler tool

L'altro strumento di profiling introdotto da Visual Studio 2012 Update 1 è denominato HTML UI Responsiveness, e permette di isolare e risolvere i problemi di performance legati alla fluidità della user interface, come codice inefficiente o eccessi nelle animazioni, nel parsing e nel calcolo del codice CSS, operazioni CPU-intensive, e così via. La foto che segue mostra un esempio di report generato tramite questo tool.

Il report utilizza diversi colori per indicare distinte categorie di eventi:

Campo Descrizione
Loading il tempo speso per caricare le risorse applicative e per il parsing iniziale del codice HTML/CSS.
Script il tempo speso per il parsing e l'esecuzione del codice JavaScript (è questa la voce che, nel nostro esempio, richiede la maggiore quantità di tempo). Questa voce include eventi DOM, timer, animazioni, ecc.
GC il tempo occupato dal garbage collector.
Styling il tempo speso per il parsing dello stile e per calcolare il layout.
Rendering il tempo impiegato per disegnare gli elementi a schermo.
Image decoding il tempo usato per la decompressione e il decoding delle immagini.
Other altre operazioni sul thread di UI.

Il grafico denominato "Diagnostic session", nella parte superiore della vista, mostra la timeline del ciclo di vita dell'applicazione con evidenziati le varie attività che influiscono sulla fluidità della UI. Anche in questo caso gli eventi rilevanti sono distinti in base al colore. Nell'immagine, si vede come l'attività di gran lunga più time-consuming sia legato all'evento di click sul pulsante (con relativa chiamata al servizio remoto fittizio).

La timeline CPU utilization mostra invece l'andamento del consumo di CPU durante l'esecuzione dell'applicazione, mentre il grafico Visual throughput mostra il numero di frame per secondo (FPS). Infine, nella parte inferiore destra della vista è possibile trovare informazioni dettagliate sui singoli eventi (come la durata, il tempo inclusivo ed esclusivo, il thread responsabile, ecc.), nonché un grafico a torta che riassume il tempo complessivamente speso per ciascuna attività.

Logging events in a Windows Store app written in JavaScript

Mentre le applicazioni Windows Store scritte in C#/VB possono sfruttare tipi e metodi che permettono di "catturare" gli eventi più significativi incontrati durante il ciclo di vita dell'applicazione per finalità di logging (questa pratica è nota anche come "audit trail" o "audit log"), per le app in HTML5/JavaScript questi meccanismi di tracing non sono accessibili.

Questo significa che, se vogliamo monitorare il comportamento di un'app, spetta a noi implementare un sistema di logging ad hoc. Di per sé, la libreria WinJS mette a disposizione solo uno scheletro vuoto, rappresentato dal metodo WinJS.log. È possibile invocare la funzione WinJS.Utilities.startLog per visualizzare messaggi di log nella console JavaScript, cosa che può tornare utile durante il debug dell'applicazione; oppure fornire una propria implementazione di questo metodo in modo da eseguire operazioni più sofisticate, come ad esempio inviare il log delle operazioni compiute e degli errori incontrati a un servizio remoto, generare email di report e così via.

Iniziamo con lo scenario più semplice, ossia l'uso del sistema di default per loggare informazioni durante il debug dell'applicazione. Prima di iniziare a loggare le operazioni, è necessario inizializzare l'oggetto WinJS.log tramite una chiamata al metodo WinJS.Utilities.startLog, come mostrato nel seguente snippet:

WinJS.Utilities.startLog();

Una volta che il log è stato inizializzato, è possibile cominciare a salvare gli eventi più rilevanti incontrati durante l'esecuzione dell'app mediante questa semplice linea di codice:

WinJS.log("my message", "info", "custom");

La funzione log riceve tre stringhe come parametri: il messaggio da loggare, uno o più tag per classificare l'evento e il tipo di evento loggato. Occorre precisare tuttavia che la riga di codice appena utilizzata non è esente da rischi, soprattutto in quelle situazioni in cui non possiamo dare per scontato che il log sia stato inizializzato. Una strada più elegante è quella di controllare l'esistenza dell'oggetto log prima di iniziare a salvare gli eventi:

WinJS.log && WinJS.log("my message", "info", "custom");

I messaggi saranno quindi visualizzati nella console JavaScript di Visual Studio, come mostrato nella prossima immagine.

L'uso di questa funzione può rivelarsi utile quando si effettua il debug dell'applicazione localmente, ma non aiuta a tenere traccia degli eventi che si verificano durante il ciclo di vita dell'app dopo che questa è stata pubblicata sul Windows Store. Ciò nonostante, come vedremo fra breve questa funzione offre un interessante entry point per la nostra implementazione custom di un sistema di logging.

Tramite il metodo WinJS.Utilities.startLog, è possibile specificare parametri addizionali utili per filtrare gli eventi da loggare. Si consideri ad esempio il seguente snippet:

WinJS.Utilities.startLog({ type: "myCustomType", action: myCustomLog });

Il parametro type indica il tipo di evento da loggare, mentre il parametro action specifica quale azione dovrebbe essere eseguita quando quel particolare tipo di evento viene sollevato. Per esempio, quando il seguente codice viene eseguito…

WinJS.log("my message", "custom", "myCustomType");

… viene invocato il metodo myCustomLog con tre parametri: il messaggio, i tag e il tipo. Questo entry point può essere sfruttato per implementare un sistema di logging custom, come mostrato nel seguente listato:

function myCustomLog(message, tag, type) {
    // codice custom, ad esempio per salvare il log
    // nello storage locale o su un servizio remoto
}

In questo modo è possibile implementare strategie differenti a seconda del tipo di evento. Ad esempio, potremmo decidere di implementare un meccanismo di log che invia a un servizio remoto solo gli eventi più critici, mentre gli eventi meno rilevanti potrebbero essere salvati nello storage applicativo e recuperate eventualmente in un secondo momento.

È anche possibile bypassare il meccanismo di default, in modo da acquisire un controllo maggiore sulle operazioni di log, come mostrato nel seguente snippet:

WinJS.log = mylogImplementation;
(...)
function mylogImplementation(message, tags, type) {
    // implementazione custom
}
(...)
WinJS.log("message", "info", "type");

In questo caso è necessario ricordarsi di chiamare il metodo WinJS.Utilities.startLog prima di iniziare il log delle operazioni.

Utilizzare i Quality Report del Windows Store per migliorare la qualità dell'applicazione

Per migliorare la qualità e monitorare le performance delle nostre app, oltre a sfruttare i dati raccolti tramite le API di WinRT viste in precedenza, è possibile contare anche su una serie di informazioni raccolte dal Windows Store dopo la pubblicazione dell'app. Il Windows Store, in particolare, espone due tipi di informazioni:

  • Analytics: si riferisce ai dati raccolti direttamente dallo store, come informazioni sul numero di download e sul rating degli utenti. Questi dati possono aiutarci a migliorare le vendite della nostra app.
  • Telemetry: fa riferimento ai dati raccolti durante l'esecuzione dell'applicazione sul device dell'utente. Questo tipo di dati fornisce informazioni su quante volte l'app è stata lanciata, per quanto tempo è stata utilizzata, se si sono verificati crash, episodi di hang (non responsività) dell'applicazione o eccezioni JavaScript. Dal punto di vista dello sviluppatore, la telemetria costituisce una preziosa fonte di informazioni che possono grandemente aiutare a migliorare la qualità e l'affidabilità di un'app.

A differenza dei dati analitici, la raccolta dei dati telemetrici può essere disabilitata nella sezione Profile della Windows Store Dashboard, come mostrato nella prossima figura.

Le informazioni analitiche relative all'applicazione vengono messe a disposizione tramite Adoption Report, i quali includono una serie di dati relative al trend dei download e ai feedback provenienti dagli utenti. Questi report includono anche informazioni sul numero di conversioni dell'app (da trial alla versione full) e degli acquisti tramite il meccanismo degli in-app purchase. Il prossimo snippet mostra un esempio di report in cui sono evidenziati i download dell'applicazione.

I dati telemetrici sono invece riassunti nei Quality Report, che misurano l'affidabilità dell'applicazione (secondo la documentazione ufficiale MSDN, i dati telemetrici sono estrapolati da un panel di circa 500 macchine selezionate in modo random). Questi report possono essere visualizzati nella sezione Quality della dashboard. Da qui, cliccando sul link Details è possibile accedere alle informazioni dettagliate circa i vari errori incontrati durante il ciclo di vita dell'app. La prossima immagine mostra un esempio di Quality Report.

Nella sezione Most Common Crashes è inoltre possibile trovare elencati i cinque più frequenti crash che hanno riguardato la nostra app, ciascuno identificato da un'etichetta. Ciascuna etichetta fornisce le seguenti informazioni: il problema che ha originato il crash, il codice errore e i simboli di debug. Accanto a ciascun errore si trova il link a un file .cab contenente il dump del processo associato a quel particolare errore. Un file di dump rappresenta uno snapshot dell'applicazione che mostra il processo in esecuzione e può essere analizzato utilizzando Visual Studio (per i dettagli sull'analisi dei file di dump si rinvia alla documentazione su MSDN).

Ti consigliamo anche