HLola: a Very Functional Tool for Extensible Stream Runtime Verification

We present HLola, an extensible Stream Runtime Verification (SRV) tool, that borrows from the functional language Haskell (1) rich types for data in events and verdicts; and (2) functional features for parametrization, libraries, high-order specification transformations, etc. SRV is a formal dynamic analysis technique that generalizes Runtime Verification (RV) algorithms from temporal logics like LTL to stream monitoring, allowing the computation of verdicts richer than Booleans (quantitative values and beyond). The keystone of SRV is the clean separation between temporal dependencies and data computations. However, in spite of this theoretical separation previous engines include hardwired implementations of just a few datatypes, requiring complex changes in the tool chain to incorporate new data types. Additionally, when previous tools implement features like parametrization these are implemented in an ad-hoc way. In contrast, HLola is implemented as a Haskell embedded DSL, borrowing datatypes and functional aspects from Haskell, resulting in an extensible engine (The tool is available open-source at http://github.com/imdea-software/hlola). We illustrate HLola through several examples, including a UAV monitoring infrastructure with predictive characteristics that has been validated in online runtime verification in real mission planning.


Introduction
Runtime Verification [4,14,18] is a dynamic technique that studies (1) how to generate monitors from formal specifications, and (2) algorithms to monitor the system under analysis, one trace at a time. Early RV specification languages were based on logics like past LTL [19] adapted to finite traces [5,10,15], regular expressions [23], fix-point logics [1], rule based languages [3], or rewriting [21]. Verdicts and many times observations in most of these specification logics are restricted to Booleans, often because most early logics in RV were borrowed from static verification-where decidability is crucial. SRV [9,22] attempts to generalize these monitoring algorithms to richer datatypes, including in observations and verdicts. SRV offers declarative specifications where offset expressions allow accessing streams at different moments in time, including future instants. Most previous SRV developments [9,11] and their extensions to event-based systems [8,11,12,17] focus on efficiently implementing the temporal engine, promising that new datatypes can be incorporated easily. However, in practice, adding a datatype requires modifying the parser, the internal representation and the runtime system. Consequently, existing tools only support a limited hardwired collection of datatypes (typically Booleans and numeric types for quantitative monitoring).
In this paper we demonstrate the tool HLola, whose core language is Lola [9], but that enables arbitrary datatypes. HLola is implemented as an embedded DSL in Haskell. Other RV tools implemented as eDSLs include [2,13] (in Scala), and [24] which implements LTL as an eDSL in Haskell. The main theoretical novelty of HLola is a technique called lift deep embedding, that consists in borrowing types transparently from Haskell and embedding the resulting language back into Haskell (see [7] for an introduction to HLola with details of the theoretical underpinnings). In fact, most HLola datatypes were introduced after the temporal engine was completed without requiring any re-implementation. An eDSL enables higher-order functions to describe transformations that produce stream declarations from stream declarations, enabling stream parametrization for free. HLola libraries collect these transformers so new logics like LTL, MTL, etc with Boolean and quantitative semantics can be implemented in a few lines (see Section 2). Haskell type-classes enable simplifiers, which can anticipate the value of an expression without requiring the computation of all its sub-expressions. Implementing these in previous systems requires to re-invent and implement features manually (like macro expansions, etc). HLola even allows specifications as data to implement "specifications within specifications" (a feature that allows computing a full auxiliary specification at every instant, useful in simulation and for nested properties). This is used in an UAV scenario to implement Kalman filters [16] as monitors that predict the trajectory of the unmanned aircraft. The output of this monitor is used to anticipate problems (using another monitor) and take preventive planning actions.
Stream Runtime Verification in a nutshell SRV generalizes monitoring algorithms to arbitrary data, where datatypes are abstracted using multi-sorted first-order interpreted signatures (called data theories in the Lola terminology). The signatures are interpreted in the sense that every functional symbol f used to build terms of a given type is accompanied with an evaluation function f (the interpretation) that allows the computation of values (given values of the arguments). A Lola specification I, O, E consists of (1) a set of typed input stream variables I, which correspond to the inputs observed by the monitor; (2) a set of typed output stream variables O which represent the outputs of the monitor as well as intermediate observations; and (3) defining equations, which associate every output y ∈ O with a stream expression E y that describes declaratively the intended values of y. The set of stream expressions of a given type is built from constants and function symbols as constructors (as usual), and also from offset expressions of the form s[k, d] where s is a stream variable, k is an integer number and d is a value of the type of s used as default. For example, altitude[-1,0.0m] represents the value of stream altitude in the previous step of time, with 0.0m as default value to be used at the initial instant. Online efficient algorithms can be synthesized for specifications with (bounded) future accesses [9,22], where efficiency means that resources (time and space) are independent of the length of the trace and can be calculated statically. HLola can be efficiently monitored in a trace-length independent sense [7]. Fig. 1 shows the software architecture of HLola. We start from an HLola specification, which can borrow datatypes, notation and features from the Haskell language (represented by the red dashed arrow in Fig. 1). A simple translator processes the specification and generates code in the Haskell eDSL. The translator does not fully parse the spec and only preforms simple rewrites, leaving most of the specification unchanged. The resulting code is combined with the HLola engine (developed in Haskell) and compiled into a binary in the target platform. A well-known downside of this approach is that during the second compilation stage, error reports may be rather cryptic. On the other hand, a Haskell expert can write specifications directly in the embedded DSL, which still resembles Lola, to finely tune an HLola specification.

The HLola Tool
The enhanced capabilities of HLola with respect to Lola (streams as data, stream type polymorphism and parametric streams) impact the syntax of the language, which diverges slightly from the syntax of the original Lola. HLola files can either be libraries or specifications: Libraries include HLola code that define streams and facilities to create streams, and must be declared using library <Name> (where <Name> is the name of the library) on the first line of the HLola file. Specifications first state the format for input and output events as format JSON or format CSV. Source files then can import libraries and stream data manipulation facilities (called theories) with the statements use library <Name> and use theory <Name> respectively. HLola files can also import arbitrary Haskell libraries using the statement use haskell <Name>, and include Haskell code directly anywhere within the blocks delimited between #HASKELL and #ENDOFHASKELL. Specifications then define the input and output streams. An Input stream is declared by its type and name in a line of the form input <Type> <name>, just like in the original Lola language. The syntax of <Type> follows the Haskell notation. An Output stream is specified by its type, name and parameters on the left hand side of =, and its defining expression on the right hand side of =: output <TypeConstraints>? <Type> <name> <args> * = <Expr>, where <TypeConstraints> is an optional set of constraints over the polymorphic types handled by the stream (expressed in Haskell notation), and <args> is an optional list of arguments of the form <Type> <name>. We can use define instead of output to define intermediate streams, whose values are not reported by the monitor but can be used by other streams. The defining <Expr> of an output stream allows the use of The auxiliary library Utils includes instantN, which stores the current instant number. Stream historically is parametrized by Boolean stream p. Once instantiated, historically p will be true until p becomes false for the first time, and will be false thereafter. This definition uses offsets to define the unrolling, using the constant value true in the first instant, lifted from Haskell as 'True. This library also contains quantitative operators like nFalses, that counts the total number of falsifications up to an instant, and percFalses that calculates the ratio of falsifications. A similar library for MTL includes the parametrized definition of ϕ U (a,b) ψ: Here the parametrized stream until takes the interval (a, b) and the streams ϕ and ψ as parameters. Similarly, the library for Quantitative MTL introduces a parametrized stream to calculate the arithmetic mean of the last k values of a given stream: which takes as parameters the window size k and the stream str. The denominator is the minimum of k and instantN, converted to Double. The numerator is the sum of the last k values in str. Polymorphosim allows us to generalize this definition to any Haskell type as long as it is Fractional, Equalizable and Streamable, using the following stream signature instead (and the same expression):

Example Specifications
In this section we show a collection of HLola specifications to demonstrate the capabilities of HLola to define stream based monitors.
Temporal Logics. HLola allows us to easily define, in a declarative way, many specifications written in temporal logic. The HLola distribution contains many LTL examples, including a sender/receiver model from [6], and other temporal logics. Consider the following MTL property from [20]: (alarm → ( [0,10] allClear ∨ [10,10] shutdown)), which includes deadlines between environment events and the corresponding system responses, stating that that an alarm is followed by a shutdown event in exactly 10 time units unless allClear is received. This is defined in HLola as follows: where willClear = eventually (0,10) allClear willShutdown = eventually (10,10) shutdown Pinescript example. TradingView is an online charting platform for stock exchange, which offers the Pinescript language to query stock time series. Pinescript queries are then run in the company's servers. We have implemented the indicators of Pinescript in HLola as a library, and we have implementated a trading strategy 6 using the HLola Pinescript library. Compared to Pinescript, HLola offers formal semantics, runtime resource guarantees (time and space) and is much more expressive, for example allowing relational queries that involve multiple stocks (their averages, etc).
UAV specifications. We have used HLola also for the online monitoring of several properties of UAVs missions. For example: (1) That the UAV does not fly over forbidden regions, and (2) that the UAV is in good position when it takes a picture. The input streams of these two specifications consist of the state of the UAV at every instant and the onboard camera events to detect when a picture is being captured. This specification imports geometric facilities from theory Geometry2D, and Haskell libraries Data.Maybe and Data.List. It then defines custom datatypes to retrieve data from the UAV, which are enclosed in a verbatim HASKELL block. The output stream all_ok_capturing assesses that, whenever the vehicle is taking a picture, the height, roll and pitch are acceptable and the vehicle is near the target location. The output stream flying_in_safe_zones reports if the UAV is flying outside every forbidden region. The output stream depth_into_poly takes the minimum of the distances between the vehicle position and every side of the forbidden region inside which the vehicle is.  Intermediate stream capturing captures whether the UAV is taking a picture (omitted for brevity). The streams filtered_pos_alt and filtered_pos represent the location and altitude of the UAV filtered to reduce noise from the sensors. We omit the definition of the filter, which is implemented in filtered_pos_component The streams height_ok, roll_ok, and pitch_ok, calculate that the corresponding attitude of the vehicle is within certain boundaries. Finally, the intermediate stream no_fly_polys obtains a set of Polygons from the input forbidden regions (its definition has been omitted), and the stream flying_in_poly returns the forbidden region in which the vehicle is flying, if any. The artifact attached to this paper includes more UAV specifications, which have been validated in real missions [25].