----------------------------------------------------------------------- -- measure -- Benchmark tools -- Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2018 Stephane Carrez -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. ----------------------------------------------------------------------- with Ada.Text_IO; with Ada.Calendar; with Ada.Containers; with Ada.Finalization; with Util.Streams.Texts; -- = Performance Measurements = -- -- Performance measurements is often made using profiling tools such as GNU gprof or others. -- This profiling is however not always appropriate for production or release delivery. -- The mechanism presented here is a lightweight performance measurement that can be -- used in production systems. -- -- The Ada package `Util.Measures` defines the types and operations to make -- performance measurements. It is designed to be used for production and multi-threaded -- environments. -- -- == Create the measure set == -- -- Measures are collected in a `Measure_Set`. Each measure has a name, a counter and -- a sum of time spent for all the measure. The measure set should be declared as some -- global variable. The implementation is thread safe meaning that a measure set can -- be used by several threads at the same time. It can also be associated with -- a per-thread data (or task attribute). -- -- To declare the measure set, use: -- -- with Util.Measures; -- ... -- Perf : Util.Measures.Measure_Set; -- -- == Measure the implementation == -- -- A measure is made by creating a variable of type `Stamp`. The declaration of -- this variable marks the begining of the measure. The measure ends at the -- next call to the `Report` procedure. -- -- with Util.Measures; -- ... -- declare -- Start : Util.Measures.Stamp; -- begin -- ... -- Util.Measures.Report (Perf, Start, "Measure for a block"); -- end; -- -- When the `Report` procedure is called, the time that elapsed between the creation of -- the `Start` variable and the procedure call is computed. This time is -- then associated with the measure title and the associated counter is incremented. -- The precision of the measured time depends on the system. On GNU/Linux, it uses -- `gettimeofday`. -- -- If the block code is executed several times, the measure set will report -- the number of times it was executed. -- -- == Reporting results == -- -- After measures are collected, the results can be saved in a file or in -- an output stream. When saving the measures, the measure set is cleared. -- -- Util.Measures.Write (Perf, "Title of measures", -- Ada.Text_IO.Standard_Output); -- -- == Measure Overhead == -- -- The overhead introduced by the measurement is quite small as it does not exceeds 1.5 us -- on a 2.6 Ghz Core Quad. -- -- == What must be measured == -- -- Defining a lot of measurements for a production system is in general not very useful. -- Measurements should be relatively high level measurements. For example: -- -- * Loading or saving a file -- * Rendering a page in a web application -- * Executing a database query -- package Util.Measures is type Unit_Type is (Seconds, Milliseconds, Microseconds, Nanoseconds); -- ------------------------------ -- Measure Set -- ------------------------------ -- The measure set represent a collection of measures each of them being -- associated with a same name. Several measure sets can be created to -- collect different kinds of runtime information. The measure set can be -- set on a per-thread data and every call to Report will be -- associated with that measure set. -- -- Measure set are thread-safe. type Measure_Set is limited private; type Measure_Set_Access is access all Measure_Set; -- Disable collecting measures on the measure set. procedure Disable (Measures : in out Measure_Set); -- Enable collecting measures on the measure set. procedure Enable (Measures : in out Measure_Set); -- Set the per-thread measure set. procedure Set_Current (Measures : in Measure_Set_Access); -- Get the per-thread measure set. function Get_Current return Measure_Set_Access; -- Dump an XML result with the measures collected by the measure set. -- When writing the measures, the measure set is cleared. It is safe -- to write measures while other measures are being collected. procedure Write (Measures : in out Measure_Set; Title : in String; Stream : in out Util.Streams.Texts.Print_Stream'Class); -- Dump an XML result with the measures collected by the measure set. -- When writing the measures, the measure set is cleared. It is safe -- to write measures while other measures are being collected. procedure Write (Measures : in out Measure_Set; Title : in String; Stream : in Ada.Text_IO.File_Type); -- Dump an XML result with the measures in a file. procedure Write (Measures : in out Measure_Set; Title : in String; Path : in String); -- ------------------------------ -- Stamp -- ------------------------------ -- The stamp marks the beginning of a measure when the variable of such -- type is declared. The measure represents the time that elapsed between -- the stamp creation and when the Report method is called. type Stamp is limited private; -- Report the time spent between the stamp creation and this method call. -- Collect the result in the per-thread measure set under the given measure -- title. procedure Report (S : in out Stamp; Title : in String; Count : in Positive := 1); -- Report the time spent between the stamp creation and this method call. -- Collect the result in the measure set under the given measure title. procedure Report (Measures : in out Measure_Set; S : in out Stamp; Title : in String; Count : in Positive := 1); -- Report the time spent between the stamp creation and this method call. -- The report is written in the file with the given title. The duration is -- expressed in the unit defined in Unit. procedure Report (S : in out Stamp; File : in out Ada.Text_IO.File_Type; Title : in String; Unit : in Unit_Type := Microseconds); private type String_Access is access String; type Stamp is limited record Start : Ada.Calendar.Time := Ada.Calendar.Clock; end record; type Measure; type Measure_Access is access Measure; type Measure is limited record Next : Measure_Access; Time : Duration; Count : Positive; Name : String_Access; end record; type Buckets_Type is array (Ada.Containers.Hash_Type range <>) of Measure_Access; type Buckets_Access is access all Buckets_Type; -- To reduce contention we only protect insertion and updates of measures. -- To write the measure set, we steal the buckets and force the next call -- to Add to reallocate the buckets. protected type Measure_Data is -- Get the measures and clear to start a new set of measures. -- Return in Time_Start and Time_End the period of time. procedure Steal_Map (Result : out Buckets_Access; Time_Start : out Ada.Calendar.Time; Time_End : out Ada.Calendar.Time); -- Add the measure procedure Add (Title : in String; D : in Duration; Count : in Positive := 1); private Start : Ada.Calendar.Time := Ada.Calendar.Clock; Buckets : Buckets_Access; end Measure_Data; type Measure_Set is new Ada.Finalization.Limited_Controlled with record Enabled : Boolean := True; pragma Atomic (Enabled); Data : Measure_Data; end record; -- Finalize the measures and release the storage. overriding procedure Finalize (Measures : in out Measure_Set); end Util.Measures;