Runtime Verification For Timed Event Streams With Partial Information

Runtime Verification (RV) studies how to analyze execution traces of a system under observation. Stream Runtime Verification (SRV) applies stream transformations to obtain information from observed traces. Incomplete traces with information missing in gaps pose a common challenge when applying RV and SRV techniques to real-world systems as RV approaches typically require the complete trace without missing parts. This paper presents a solution to perform SRV on incomplete traces based on abstraction. We use TeSSLa as specification language for non-synchronized timed event streams and define abstract event streams representing the set of all possible traces that could have occurred during gaps in the input trace. We show how to translate a TeSSLa specification to its abstract counterpart that can propagate gaps through the transformation of the input streams and thus generate sound outputs even if the input streams contain gaps and events with imprecise values. The solution has been implemented as a set of macros for the original TeSSLa and an empirical evaluation shows the feasibility of the approach.


Introduction
Runtime verification (RV) is a dynamic formal method for software system reliability. RV studies how to analyze and evaluate traces against formal specifications and how to obtain program traces from the system under observation, e.g., through software instrumentation or utilization of processors' embedded trace units. Since RV only inspects one execution trace of the system, it is often regarded to be a readily applicable but incomplete approach, that combines formal verification with testing and debugging.
Most early RV languages were based on logics common in static verification, like LTL [21], past LTL adapted for finite paths [19,12,4]  The intermediate stream cond is derived from the input streams indicating if reset has currently the most recent event, and thus the sum should be reset to 0. If the input streams contain gaps (dotted regions on the right) some information can no longer be computed, but after a reset event the computation recovers from the data loss during the gap. denotes events with unknown data.
or timed regular expressions [2]. For these logics, the monitoring problem consists on computing a Boolean verdict indicating whether the trace fulfills the specification. In contrast to static analysis, however, considering only a single concrete trace enables the application of more complex analyses: Stream Runtime Verification (SRV) [11,6,7] uses stream transformations to derive additional streams as verdicts from the input streams. Using SRV one can still check if the input stream is conformant with a specification, but additionally verify streams in terms of their events' data: streams in SRV can store data from richer domains than Booleans, including numerical values or user defined data-types, so SRV languages can extract quantitative values and express quantitative properties like "compute the average retransmission time" or "compute the longest duration of a function". SRV cleanly separates the temporal dependencies that the stream transformation algorithms follow from the concrete operations to be performed on the data, which are specific to each data-type. As an example for SRV consider the trace diagram on the left of Fig. 1. We consider non-synchronized event streams, i.e., sequences of events with increasing timestamps and values from a data domain. Using non-synchronized event streams one can represent events arriving on different streams with different frequencies in a compact way with little computation overhead because there is no need to process additional synchronization events in the stream-transformation process. In this paper we use the TeSSLa specification language [7], an SRV language for non-synchronized, timed event streams. TeSSLa has been defined to be general enough to allow for a natural translation from other common SRV formalisms, e.g., Lola [11] and Striver [17]. Therefore, our results carry over to these languages as well.
Since RV is performed on traces obtained from the system under test in the deployed environment, it is a common practical problem for RV techniques that the traces do not cover the entire run of the system. However, most of the previous RV approaches require the trace to be available without any interruptions in order to obtain a verdict, because this knowledge is assumed in the semantics of the specification logics. Especially in the case of interrupted traces with some data losses applying previous RV techniques can be very challenging. Unfortunately those traces occur very often in practical testing and debugging scenarios, e.g., due to interrupted experiments, buffer overflows, network errors or any other temporary problem with the trace retrieval.
In this paper we present a solution to the problem of evaluating traces with imprecise values and even interrupted traces. Our only assumption is that we have exact knowledge of the imprecision of the trace in the following sense: (1) for events with imprecise values we know the range of values and (2) for data losses we know when we stop getting information and when the trace becomes reliable again. We call such a sequence of uncertainty a gap in the trace. Our solution automatically propagates gaps and imprecisions, and allows to obtain sound verdicts even in the case of missing information in the input trace. The assumption is reasonable in our target application: online non-intrusive monitoring low-level embedded software. Fig. 1 on the right displays a case where the input stream values has a long gap in the middle. It is not possible to determine the events in the output stream sum during that gap, because we do not even know if and how many events might have happened during that gap. Thus, the intermediate stream cond and the output stream sum simply copy that gap representing any possible combination of events that might occur. The first event after the gap is the one with the value 3 on values. Because no reset happened after the end of the gap, we would add 3 to the latest event's value on sum, but the gap is the latest on sum. Thus, we only know that this input event on values causes an event on sum independently of what might have happened during the gap, but the value of that event completely depends on possible events occurring during the gap. After the next event on reset the values of the following events on sum are independent of any previous events. The monitor can fully recover from the missing information during the gap and can again produce events with precise values.
In order to realize this propagation of gaps through all the steps of the streamtransformation we need to represent all potentially infinitely many concrete traces (time is dense and values are for arbitrary domains) that might have happened during gaps and imprecise events. An intuitive approach would be a symbolic representation in terms of constraint formulas to describe the set of all possible streams. These formulas would then be updated while evaluating the input trace. While such a symbolic execution might work for shorter traces, the representation can grow quickly with each input event. Consequently the computational cost could grow prohibitively with the trace length for many input traces. Instead, in this paper we introduce a framework based on abstraction [9,10]. We use abstraction in two ways: (1) Streams are lifted from concrete domains of data to abstract domains to model possible sets of values. For example, in our solution a stream can store intervals as abstract numerical values. (2) We define the notion of abstract traces, which extend timed streams with the capabilities of representing gaps. Intuitively, an abstract trace over-approximates the sets of concrete traces that can be obtained by filling the gaps with all possible concrete events. Our approach allows for both gaps in the input streams as well as events carrying imprecise values. Such imprecise values can be modelled by abstract domains, e.g., intervals of real numbers. Since we rely on abstraction, we can avoid false negatives and false positives in the usual sense: concrete verdicts are guaranteed to hold and imprecise verdicts are clearly distinguished from concrete verdicts. The achievable precision depends on the specification and the input trace.
After reproducing the semantics of the basic TeSSLa operators in Section 2, we introduce abstract semantics of the existing basic operators of TeSSLa in Section 3. Using these abstract TeSSLa operators, we can take a TeSSLa specification on streams and replace every TeSSLa operator with its abstract counterpart and derive an abstraction of the specification on abstract event streams. We show that the abstract specification is a sound abstraction of the concrete specification, i.e., every concrete verdict generated by the original specification on a set S of possible input traces is represented by the abstract verdict applied to an abstraction of S. We further show that the abstract TeSSLa operators are a perfect abstraction of their concrete counterparts, i.e., that applying the concrete operator on all individual elements of S doesn't get you more accurate results. Finally, we show that an abstract TeSSLa specification can be implemented using the existing TeSSLa basic operators by representing an abstract event stream as multiple concrete event streams carrying information about the events and the gaps. Since the perfect accuracy of the individual abstract TeSSLa operators does not guarantee perfect accuracy of their compositions, we discuss the accuracy of composed abstract TeSSLa specifications in Section 4. Next we present in Section 5 an advanced use-case where we apply abstract TeSSLa to streams over a complex data domain of unbounded queues, which are used to compute the average of all events that happened in the sliding window of the last five time units. In the final Section 6 we evaluate the overhead and the accuracy of the abstractions presented in this paper on representative example specifications and corresponding input traces with gaps.
Related Work. SRV was pioneered by LOLA [11,14,15]. TeSSLa [7] generalises to asynchronous streams the original idea of LOLA of recursive equations over stream transformations. Its design is influenced by formalisms like stream programming languages [18,5,16] and functional reactive programming [13]. Other approaches to handle data and time constraints include Quantitative Regular Expressions QRE [1] and Signal Temporal Logic [20].
While ubiquitous in practice, the problem of gaps in an observation trace has not been studied extensively. To the best of our knowledge, abstraction techniques have not been applied to the evaluation of stream-based specifications. However, approaches to handle the absence of events or ordering information have been presented for MTL [3] and past-time LTL [24]. State estimation based on Markov models has been applied to replace absent information by a probabilistic estimation [23]. The concept of abstract interpretation used throughout this paper has been introduced in [8].

The TeSSLa Specification Language
A time domain is a totally ordered semi-ring (T, 0, 1, +, ·, ≤) that is positive, i.e., ∀ t∈T 0 ≤ t. We extend the order on time domains to the set T ∞ = T ∪ {∞} with ∀ t∈T t < ∞. Given a time domain T, an event stream over a data domain D is a finite or infinite sequence where D ⊥ := D ∪ {⊥} and t i < t i+1 for all i with 0 < i + 1 < |s| (|s| is ∞ for infinite sequences). An infinite event stream is an infinite sequence of timestamps and data values representing the stream's events. A finite event stream is a finite sequence of timestamped events up to a certain timestamp that indicates the progress of the stream. A stream can end with: -a timestamp without a data value that denotes progress up to but not including that timestamp, -a timestamp followed by ⊥ (or a data value) which denotes progress up to and including that timestamp (and an event at that timestamp), -∞, which indicates that no additional events will ever arrive on this stream. We refer to these cases as exclusive, inclusive and infinite progress, resp. Streams s ∈ S D can be seen as functions s : is a value d if s has an event with value d at time t or ⊥ if there is no event at time t. For timestamps after the progress of the stream s(t) is ?. Formally, s(t) = d if s contains td, s(t) = ⊥ if s does not contain t, but contains a t > t or s ends in t⊥, and s(t) = ? otherwise. We use ticks(s) for the set {t ∈ T | s(t) ∈ D} of timestamps where s has events. A stream s is a prefix of stream r if ∀ t∈T s(t) ∈ {r(t), ?}. We use the unit type U = { } for streams carrying only the single value .
A TeSSLa specification consists of a collection of stream variables and possibly recursive equations over these variables using the operators nil, unit, time, lift, last and delay. The semantics of recursive equations is given as the least fixedpoint of the equations seen as a function of the stream variables and fixed input streams. See [7] for more details and Appendix A for an elaborated example. nil = ∞ ∈ S ∅ is the stream without any events and infinite progress. unit = 0 ∞ ∈ S U is the stream with a single unit event at timestamp zero and infinite progress.    Figure 1 in TeSSLa. Let resets ∈ S U and values ∈ S Z be two external input event streams. We then derive cond ∈ S B and lst, sum ∈ S Z as follows: Using the operators described above one can only derive streams with timestamps that are already present in the input streams. To derive streams with events at computed timestamps one can use the delay operator, which is described in Appendix B.
If (α, γ) is a Galois Connection between A and B, the function f # : In this section we define the abstract counterparts of the TeSSLa operators, listed in Section 2. A data abstraction of a data domain D is an abstract domain D # with an element ∈ D # and an associated concretisation function γ : D # → 2 D with γ( ) = D. The abstract value represents any possible value from the data domain and can be used to model an event with known timestamp but unknown value. A gap is a segment of an abstract event stream that represents all combinations of events that could possibly occur in that segment (both in terms of timestamps and values). Hence an abstract event stream consists of an event stream over a data abstraction and an associated set of known timestamps: Definition 1 (Abstract Event Stream). Given a time domain T, an abstract event stream over a data domain D is a pair (s, ∆) with s ∈ S D # and ∆ ⊆ T such that ∆ can be represented as union of intervals whose (inclusive or exclusive) boundaries are indicated by events in an event stream. Further, we require s(t) = ⊥ ⇒ t ∈ ∆. The set of all abstract event streams over D is denoted as P D . The concretisation function γ : If the data abstraction is defined in terms of a Galois Connection a refinement ordering and abstraction function can be obtained. The refinement ordering Note, if the data abstraction is defined in terms of a Galois Connection, (α, γ) is a Galois Connection between 2 S D and P D .
An abstract event stream s = (s , ∆) ∈ P D can also be seen as a function s : otherwise. A particular point t of an abstract event stream s can be either (a) directly at an event (s(t) ∈ D), (b) in a gap (s(t) = ), (c) in a gapless segment without an event at t (s(t) = ⊥), or (d) after the known end of the stream (s(t) = ?). We If D # is a data abstraction of a data domain D with an associated concretisation function γ, then D # ⊥ is a data abstraction of D ⊥ with an associated concretisation function γ ⊥ : The above diagram shows a possible data abstraction B # of B and the corresponding data abstraction B # ⊥ . Using the functional representation of an abstract event stream we can now define the abstract counterparts of the TeSSLa operators: nil # = (∞, T) ∈ P ∅ is the empty abstract stream without any gaps. unit # = (0 ∞, T) ∈ P U is the abstract stream without any gaps and a single event at timestamp 0.
time # : P D → P T , time # (s) := z is equivalent to its concrete counterpart; only the data domain is extended: . . , s n ) := z can be defined similarly to its concrete counterpart, because the abstract function f # takes care of the gaps: The operator lift # is restricted to those functions f # that are an abstraction of functions f that can be used in lift, that is, f (⊥, . . . , ⊥) = ⊥. Using the abstract lift we can derive the abstract counterparts of const and merge: = otherwise maps all events' values to a constant while preserving the gaps. Using const # we can define constant signals without any gaps, e.g., x y z The diagram on the right shows an example trace merging the events of the streams x and y. The symbol • indicates a point-wise gap. Note how an event on the first stream takes precedence over a gap on the second stream, but not the other way round, similarly to how events from the first stream are prioritized if both streams have an event at the same timestamp. last # : P D1 × P D2 → P D1 , last # (v, r) := z has three major extensions over its concrete counterpart: (1) is added as an output in case an event on r occurs and there were events on the stream v of values but all followed by a gap. (2) is outputted for all gaps on the stream r of trigger events if there have been events on the stream v of values. (3) can also be output if an event occurs on r and no event occurred on v before except for a gap. The parts similar to the concrete operator are typeset in gray: The trace diagram on the right shows an example trace covering most edge cases of the abstract last. The output stream z is a point-wise gap if triggered after initial gaps (3); z is if triggered after noninitial gaps (1); z is an event if triggered after a gapless sequence (d); and z inherits all gaps from the stream of trigger events (2).
We can now combine the last # and the lift # operators to realize: abstract signal lift for total functions f : Example 2. By replacing every TeSSLa operator in Example 1 with their abstract counterparts and applying it to the abstract input streams values ∈ P Z and resets ∈ P U , we derive the abstract stream cond ∈ P B and the recursively derived abstract stream sum ∈ P Z : After the large gap on values, the sum stream eventually recovers completely. The first reset after the point-wise gap does not lead to full recovery, because at that point the last event on values cannot be accessed, because of the prior gap. The next reset falls into the gap, so again cond cannot be evaluated. In a similar fashion one can define an abstract delay # operator as counterpart of the concrete delay. See Appendix B for details. Following from the definitions of the abstract TeSSLa operators we get: Theorem 1. Every abstract TeSSLa operator is an abstraction of its concrete counterpart.
Theorem 1 implies that abstract TeSSLa operators are sound in the following way. Let o be a concrete TeSSLa operator with the abstract counterpart o # and let s ∈ P D be an abstract event stream with a concretization function γ. Then, o(γ(s)) γ(o # (s)). Since abstract interpretation is compositional we can directly follow from the above theorem: Corollary 1. If a concrete TeSSLa specification ϕ is transformed into a specification ψ by replacing every concrete operator in ϕ with its abstract counterpart, then ψ is an abstraction of ϕ.
Theorem 1 guarantees that applying abstract TeSSLa operators to the abstract event stream is still sound regarding the underlying set of possible concrete event streams. However, we have established no result so far about the accuracy of the abstract TeSSLa operators. The abstraction returning only the completely unknown stream (∆ = ∅) is sound but useless. The following theorem states, that our abstract TeSSLa operators are optimal in terms of accuracy. Using a perfect abstraction guarantees the abstract TeSSLa operators preserve as much information as can possibly be encoded in the resulting abstract event streams.
Theorem 2. Every abstract TeSSLa operator is a perfect abstraction of its concrete counterpart.
Given a concrete TeSSLa operator o and its abstract counterpart o # , and any abstract event stream s ∈ P D with the Galois Connection (α, γ) between 2 S D and P D one can show that o # (s) = α(o(γ(s)). Applying the abstract operator on the abstract event stream is as good as applying the concrete operator on every possible event stream represented by the abstract event stream. Thus o # is a perfect abstraction of o. (The detailed proof can be found in Appendix C.) Note that we assume that f # is a perfect abstraction of f to conclude that lift # (f # ) is a perfect abstraction of lift(f ).
In Corollary 1 we have shown that a specification ψ (generated by replacing the concrete TeSSLa operator in ϕ with their abstract counterparts) is an abstraction of ϕ. Note that ψ is in general not a perfect abstraction of ϕ. We study some special cases of perfect abstractions of compositional specifications in Section 4.
The next result states that the abstract operators can be defined in terms of concrete TeSSLa operators. Realizing the abstract operators in TeSSLa does not require an enhancement in the expressivity of TeSSLa. Proof. One can observe that the abstract TeSSLa operators are monotone and future independent (the output stream up to t only depends on the input streams up to t.) As shown in [7], TeSSLa can express every such function.

Fixpoint Calculations Ensuring Well-Formedness
A concrete TeSSLa specification consists of stream variables and possibly recursive equations applying concrete TeSSLa operators to the stream variables. Theorem 1 and Corollary 1 guarantee that a concrete TeSSLa specification can be transformed into an abstract TeSSLa specification, which is able to handle gaps in the input streams. Additionally, Theorem 3 states that the abstract TeSSLa operators can be implemented using concrete TeSSLa operators. Combining these two results, one can transform a given concrete specification ϕ into a corresponding specification ψ, which realizes the abstract TeSSLa semantics of the operators in ϕ, but only uses concrete TeSSLa operators.
However, using the realization of the abstract TeSSLa operators in TeSSLa adds additional cyclic dependencies in ψ between the stream variables. A TeSSLa specification is well-formed if every cycle of its dependency graph contains at least one edge guarded by a last (or a delay) operator, which is required to guarantee the existence of a unique fixed-point and hence computability (see [7]). Consider the trace diagram on the right showing last # (v, r). If v is used in a recursive manner, i.e., v is defined in terms of last # (v, r), then the first event on v could start a gap on last # (v, r) that could start a gap on v at the same timestamp. As a result v has an unguarded cyclic dependency and hence the specification is not well-formed. To overcome this issue one can split up the value and gap calculation sequentially, reintroducing guards in the cyclic dependency: Definition 2 (Unrolled Abstract Last). We define two variants of the abstract last, last # ⊥ and last # as follows.
Function last # ⊥ executes a normal calculation of the events, in the same way an abstract last would do, but neglecting gaps and outputting ⊥ as long as there is no event. Function last # takes a third input stream and outputs its events directly, but calculates gaps correctly as last # would do.
Since the trigger input of a last operator cannot be recursive in a well-formed specification, a recursive equation using one last has the form x = last # (v, r) and v = f (x, c), where c is a vector of streams not involved in the recursion and f does not introduce further last (or delay) operators. Now, this equation system can be rewritten in the following equivalent form: This pattern can be repeated if multiple recursive abstract lasts are used and can also be applied in a similar fashion to mutually recursive equations and the delay operator.

Perfection of Compositional Specifications
A concrete TeSSLa specification ϕ can be transformed into an abstract TeSSLa specification ψ by replacing the concrete operators with their abstract counterparts. For two functions f and g with corresponding abstractions f # and g # the function composition f # • g # is an abstraction of f • g. Unfortunately, even if f # and g # are perfect abstractions, f # • g # is not necessarily a perfect abstraction. Hence, ψ needs not be a perfect abstraction of ϕ. In this section we discuss the perfection of two common compositional TeSSLa operators: (1) the slift # defined in Section 3 is a composition of last # in lift # , which realizes signal semantics; (2) last # (time # (v), r), which is a common pattern used when comparing timestamps.
The slift # is defined as the lift # applied to the synchronized versions x and y of the input streams x and y. The input stream x is synchronized with y by keeping the original events of x and reproducing the last known value of x for every timestamp with an event on y, but not on x.
Theorem 4. If f # is a perfect abstraction of f then slift(f # ) # is a perfect abstraction of slift(f ).
Proof. Since slift # is defined on abstract event streams we need to consider gaps. The stream x does not have any gap or event until the first gap or event on x. After the first gap or event on x the synchronized stream x contains a gap or event at every timestamp where x or y contain a gap or event. Because slift # is symmetric in terms of the event pattern the same holds for y . By definition, slift # (f # )(x, y) = z contains an event or gap iff x and y contain an event or gap, because f is a total function. The output stream z contains an event iff x and y contain events. The events values are ensured to be as precise as possible, because f # is a perfect abstraction of f .
TeSSLa allows arbitrary computations on the timestamps of events us- if y(t) = with a = inf{t < t | ∀ t <t <t v(t ) = } and b = max{t < t | t ∈ ticks(v)} and z(t) = y(t) otherwise. Now the following result holds (the proof can be found in Appendix C).
Theorem 5. lastTime # is a perfect abstraction of lastTime.
A similar problem occurs if slift # is used to compare event's timestamps. In Example 2 the stream cond derived by comparing the timestamps of values and resets has two events with the unknown data value because of prior gaps on values. Since the slift # is defined in terms of lift # and last # we can define the function sliftTime # (f # )(x, y) as an abstraction for the special case sliftTime(f )(x, y) = slift(f )(time(x), time(y)) by using lastTime # instead of last # and ensuring that f # uses interval arithmetics to abstract f . Note that sliftTime # (f # ) is a perfect abstraction of sliftTime(f ).

Abstractions for Sliding Windows
In this section we demonstrate how to apply the techniques presented in this paper to specifications with richer data domains. In particular, we show now a TeSSLa specification that uses a queue to compute the average load of a processor in the last five time units. The moving window is realized using a queue storing all events that happened in the time window. The stream load ∈ S R contains an event every time the input load changes: The queue operation enq adds elements to the queue, while remOlder 5 removes elements with a timestamp older than five time units. The function int accumulates all values in the queue weighted by the length of the corresponding signal piece. The queue operation fold is used to fold the function f over all elements from the queue with the initial accumulator 0 until the timestamp u. Hence f is called for every element in the queue with the timestamps a and b, the element's value v and the accumulator. Consequently, the specification adds elements to the queue, removes the expired elements and accumulates the remaining values. Using our approach we replace every operator with its abstract counterpart and represent abstract queues appropriately such that also queues with partly unknown entries can be modeled. By doing this we obtain a specification that is able to handle gaps in the input stream, as illustrated in Fig. 2. We can extend the example such that the queue only holds a predefined maximum number of events (to guarantee a finite state implementation). When removing events we represent these as unknown entries in the abstract queues. The abstract fold # is capable of computing the interval of possible average loads for queues with unknown elements anyhow.
Note that the average load is only updated for every event on the input stream. Using a delay operator, we can set a timeout whenever an element leaves the sliding window in the abstract setting. The element is removed from the queue at that timeout and the new value of the queue is updated with the remaining elements. Formal definitions of the queue functions as well as the complete specifications are available online 3 .

Implementation and Empirical Evaluation
As discussed in Section 3.1 the abstract TeSSLa operators can be implemented using only the existing concrete TeSSLa operators. We implemented the abstract TeSSLa operators as macros specified in the TeSSLa language itself such that the existing TeSSLa engine presented in [7] can handle abstract TeSSLa specifications. An abstract event stream (s, ∆) ∈ P D can be represented as two TeSSLa streams s ∈ S D # and s d ∈ S X , where X contains the following six possible changes of ∆: inclusive start, exclusive start, inclusive end, exclusive end, point-wise gap and point-wise event in a gap. Using this encoding it is sufficient to look up the latest s d (t ) with t ≤ t to decide whether t ∈ ∆. While this encoding already allows a decent implementation of abstract TeSSLa we go one step further and assume a finite time domain with a limited precision, e.g., 64 bit integers or floats. Under this assumption there is always a known smallest relative timestamp ε. Hence, we can use the encoding s d ∈ S B where an event s d (t) = true encodes a start inclusive and s d (t) = false an end exclusive. This encoding captures the most common cases and simplifies the implementation of union and intersection on ∆ enormously since they can now be realized as slift(∨) and slift(∧), resp. The other possible switches at timestamp t can be represented as follows: s d (t + ε) = true encodes an exclusive start, s d (t + ε) = false encodes an inclusive end, s d (t) = true and s d (t + ε) = false encodes a point-wise event in a gap, and s d (t) = false and s d (t + ε) = true encodes a point-wise gap. Using this encoding the abstract TeSSLa operators do not need to handle these additional cases explicitly.
Furthermore, assuming the smallest relative timestamp ε, we can avoid the need to perform the unrolling defined in Defs. 2 by delaying the second part of the computation to the next possible timestamp t + ε.
As a final efficiency improvement we simplified last # before the first event on the stream of values, which are not relevant in practice. The abstract operator and hence abstract specifications are of course still a sound abstraction of their concrete counterparts, but due to over-abstractions no longer a perfect one during this initial event-less phase of the stream of values.
The implementation in form of a macro library for the existing TeSSLa engine is available together with all the examples and scripts used in the following empirical evaluation and can be experimented with in a web IDE 4 .
In the following empirical evaluation we measure the accuracy of the abstractions presented in this paper. An abstract event stream represents input data with some sequences of data loss, where we do not know if any events might have been occurred or what their values have been. Applying an abstract TeSSLa specification to such an input stream takes these gaps into account and provides output streams that in turn contain sequences of gaps and sequences containing concrete events. To evaluate the accuracy of this procedure we compare the output of an abstract TeSSLa specification with the best possible output.
Let r ∈ P D be an abstract event stream. We obtain the set R of all possible input streams containing all possible variants that might have happened during gaps in r by applying the concretization function γ on the abstract input stream. Now we can apply the concrete TeSSLa specification ϕ to all streams in R and get the set S of concrete output streams. On the other hand we apply the abstract TeSSLa specification ϕ # directly to r and get the abstract output stream s. Now S is the set of all possible output streams and γ(s) is the set of output streams defined by the abstract TeSSLa specification. The diagram on the right depicts this comparison process. To compare γ(s) and S in a quantitative way we define the ignorance measure ι : 2 S D → I = [0, 1] scoring the ambiguity of such a set of streams, i.e., how similar the different streams in the set are. Events in non-synchronized streams might not have corresponding events at the same timestamp on the other streams. Hence we refer to the signal semantics of event streams where the events represent the changes of a piece-wise constant signal. As depicted on the right with three event streams over the finite data domain {0, 1, 2}, we score timestamps based on how many event streams have the same value with respect to the signal semantics at that timestamp. These scores are then integrated and normalized throughout the length of the streams. See Appendix E for the technical details. Using this ignorance measure we can now compute the optimal ignorance i := ι(S) ∈ I and the ignorance k := ι(γ(s)) ∈ I of the streams produced by the abstract TeSSLa specification.
For the evaluation we took several example specifications and corresponding input traces representing different use-cases of TeSSLa and compared the optimal ignorance with the ignorance of abstract TeSSLa. Note that computing the optimal ignorance requires to derive all possible variants of events that might have happened during gaps, which are in general infinitely many and in the special case of only point-wise gaps still exponentially many. Hence this can only be done on rather short traces with only a few point-wise gaps. As a measure for the overhead imposed by using the abstraction compared to the concrete TeSSLa specification we use the computation depth, i.e., the depth of the dependency graph of the computation nodes of the specifications. While runtimes are highly depending on implementation details of the used TeSSLa engines, the computation depth is a good indicator for the computational overhead in terms of how many concrete TeSSLa operators are needed to realize the abstract TeSSLa specification. See Fig. 3 for a graphical representation and Appendix F for numerical results. The first three examples represent the class of common, simple TeSSLa specifications without complex interdependencies and no generation of additional events with delay: Reset-count counts between reset events; reset-sum sums up events between reset events; and filter-example filters events occurring in a certain timing-pattern. For these common specifications the overhead is small and the abstraction is perfectly accurate. The burst example checks if events appear according to a complex pattern. In the abstraction we loose accuracy because the starting point of a burst is not accessible by last # after a gap. A similar problem occurs in the queue example where we use a complex data domain to develop a queue along an event stream. If last # produces after a gap all information about the queue before the gap is lost. For variable-period the abstraction is not perfectly accurate, because the delay is used to generate events periodically depending on an external input. This gets even worse for the self-updating queue where complex computations are performed depending on events generated by a delay. Surprisingly, the finite-queue is again perfectly accurate, because the size of the queue is limited in a way that eliminates the inaccuracy of the abstraction in this particular example.

Conclusion
By replacing the basic operators of TeSSLa with abstract counterparts, we obtained a framework where properties and analyses can be specified with respect to complete traces and automatically evaluated for partially known traces. We have shown that these abstract operators can be encoded in TeSSLa, allowing existing evaluation engines to be reused. This is particularly useful as TeSSLa comprises a very small core language suitable for implementation in soft-as well as hardware. Using the example of sliding windows, we demonstrated how complex data structures like queues can be abstracted. Using finite abstractions, our approach even facilitates using complex data structures when only limited memory is available. Evaluating the abstract specification typically only increases the computational cost by a constant factor. In particular, if a concrete specification can be monitored in linear time (in the size of the trace) its abstract counterpart can be as well. Finally, we illustrated the practical feasibility of our approach by an empirical evaluation using the freely available TeSSLa engine. Note that y 0 y 1 y 2 y 3 y 4 y 5 regarding the prefix relation.

B Delay
In Section 2 TeSSLa was introduced with the operators nil, unit, time, lift and last. As discussed in [7] this set of operators is sufficient to express timestamp conservative functions, i.e., functions which do not introduce additional timestamps. In order to express arbitrary functions which can add events at arbitrary timestamps, TeSSLa was introduced in [7] with the delay operator. The delay operator was removed from the main content of this paper due to space limitations and the amount of technical details needed to define the concrete and abstract operators. Since these two operators nevertheless fit very seamless into the abstraction framework presented in this paper they are included in the implementation and the empirical evaluation and their definitions are given in this appendix.

B.1 Concrete Delay
delay : S T\{0} × S D → S U , delay(d, r) := z takes a delay stream d and a reset stream r. It emits a unit event in the resulting stream after the delay passes. Every event on the reset stream resets any delay. New delays can only be set together with a reset event or an emitted output event. Formally, ? otherwise with the following auxiliary functions: For an example on how to use the delay operator in a TeSSLa specification see Section D.4.

B.2 Abstract Delay
In Section 3 of this paper we presented the abstract operators lift # and last # for their concrete counterparts lift and last. In a similar way we now define the abstract delay # operator as counterpart of the concrete delay. delay # : P T∞\{0} × P D → P U , delay # (d, r) := z needs additional logic to handle events on the delay stream and gaps on the input streams. Since the output stream is of type P U no additional logic to produce events is needed. We introduce the following additional auxiliary functions: (1) maybeEvent, which captures whether there may be an event that would cause the delay to hold: (2) maybeBot, which captures whether it is plausible that the stream z being defined does not tick: The conjunction of these two predicates captures the gap case. Again, the parts similar to the concrete operator are typeset in gray: The trace diagram on the right shows an example covering most of the interesting edge cases of the abstract delay: (1), (2) the gap on d has no effect; (3) the gap on r makes it unclear if a delay starts, a gap is created on z; (4) the gap on r makes it unclear if reset happens, a gap is created on z. (5) causes unknown delay which results in a gap on z until next event happens on r; (6) the gap on r starts unknown delay because of gap on d, reset event on r does not help because gap on d remains. Results in long gap on z until event on r happens out of the gap on d. Consider the example for z = delay # (d, r) shown on the right. If the delay produces an event after or during a gap on the reset stream, a potential reset event might cancel the delay earlier, creating point-wise gaps. The concrete delay has only one active delay at any given point in time. However, to implement the abstract delay infinite memory may be needed to keep track of all potential delays, because it may not be known whether a delay has been canceled or started earlier by a reset event. By keeping track only of the minimal and maximal potential delays we get a finite-memory implementation. However, this implementation may be an imperfect abstraction of the delay operator which produces larger gaps. Those gaps marked in red on the right are replaced with one large gap.

B.3 Finite State Implementations of Delay
Definition 4 (Finite Memory Abstract Delay). We define delay # fin (d, r) = z to be the same as delay # (d, r) except that z(t) := (instead of ⊥) if ∃ t <t t + d(t ) ≤ t ∧ r(t ) = and ∀ t |t +d(t )≤t <t z(t ) = and t |t <t <t t ∈ ticks(r) and ∃ t <t t + d(t ) ≥ t ∧ r(t ) = .

B.4 Unrolling Delay
In Section 3.1 we discussed that using the abstract TeSSLa operators might introduce additional cyclic dependencies between the stream variables. In order to overcome this issue we presented translation in Definition 2 which reintroduces guards in the cyclic dependency by unrolling the value and gap calculation sequentially. The same approach can be applied to unroll the abstract delay # operator: Definition 5 (Unrolled Abstract Delay). We define two variants of the abstract delay, delay # ⊥ and delay # as follows: Let z = delay # (d, r), then delay # ⊥ (d, r) := z ⊥ and delay # (d, r, p) := z .
Since the trigger input of a delay operator cannot be recursive in a well-formed specification, a recursive equation using one last has the form x = delay # (d, r) and d = f (x, c), where c is a vector of streams not involved in the recursion and f does not introduce further lasts or delays. Now, this equation system can be rewritten in the following equivalent form: By doing this, the calculation of the value and the domain of the definition is split in two parts preserving the semantics. The same pattern can be repeated if multiple recursive abstract lasts or delays are used.
There is a remaining case to be considered, when f contains other abstract lasts or delays which lead to mutual recursive specifications. In this case, these other abstract lasts and delays have to use x, d or r as their input instead of the primed counterparts because the computation requires the complete last or delay stream, respectively. Furthermore, there may be more unrolling steps needed than just one to unroll the whole specification but every unrolling step still follows the previously described pattern for last and delay.

C Missing Proofs
Theorem 2. Every abstract TeSSLa operator is a perfect abstraction of its concrete counterpart.
Proof. We need to show that for all partial streams s 1 , . . . , s n with s i ∈ P Di i and every TeSSLa operator f and its abstract counterpart f # it holds that f # (s 1 , . . . , s n ) = α(f (γ(s 1 ), . . . , γ(s n ))). We use the Galois Connection (α, γ) as presented in Definition 1. For nil and unit this holds trivially because they are equal to their abstract counterparts. So it remains to show this for time, lift, last and delay. Note that because the Galois Connection maps from the abstract lattice to the powerset of the (concrete) lattice, we assume all operators in the following are naturally extended to sets.
time # (s 1 ) = α(time(γ(s 1 ))) holds because time # changes nothing regarding the abstract elements: gaps are copied and the values of the events do not matter because they are replaced with the timestamps.
lift # (g # )(s 1 , . . . , s n ) = α(lift(g)(γ(s 1 ), . . . , γ(s n ))) holds if g # is a perfect abstraction of g, because the values and gaps are only passed to g # in the abstract case which calculates the values and gaps.
last # (s 1 , s 2 ) = α(last(γ(s 1 ), γ(s 2 ))) holds because the only special case regarding the data abstraction is when a trigger occurs and there is a gap on the stream of values after the last event. Then is the output which is fine because in the concretization the gap is replaced with arbitrary values. Other then that, the data abstraction is not important because in last # the data values are only copied to the new events on the output stream.
the only special case regarding the gaps is a trigger event before any event on the stream of values but after a gap on said stream, which results in a gap. This is fine because the gap on the stream of values is later replaced with an arbitrary or no value which is then the output when the trigger event occurs. Thus using α later on this set of streams, we get a gap where the trigger event was. After the first event on the stream of values, the gaps from the stream of trigger events are just copied.
delay # (s 1 , s 2 ) = α(delay(γ(s 1 ), γ(s 2 ))) holds because data abstraction wise, the output can always only be unit. If we look at the gaps, there are only a few cases where delay # produces gaps: Point-gaps are created when a delay times out which (1) is set via a gap or (2) is set normally but a gap occurs on the reset stream before the timeout. Big gaps which last until the next event on the reset stream are created when a delay can be set but on the delay stream is (3) an event with as value or (4) a gap. This perfectly fits to how α(delay(γ(d), γ(r))) works because in (1) the concretization function creates sets of streams where the delay is set not at all or with a value. The delay then outputs a set of streams where sometimes there is an output event and sometimes not which results in a point-gap again when the abstraction function is used.
(2) the concretization function creates sets of streams where where a reset and no reset happens before the timeout. This has the same implications as for (1). (3) there is a delay started with as value. Then the concretization function creates a set of streams having all possible values as delay at that point. Hence, delay results in the set of streams that have an arbitrary amount of events between the point where the delay is set and the next reset event.
Using the abstraction function again this results in a big gap. (4) it is the same case as (3) but the set of streams created by the concretization function contains additionally those streams with no value as delay. This has the same implications as in (3).
Proof. We need to show that for all partial streams v, r the following equation holds: lastTime # (v, r) = α(lastTime(γ(v), γ(r))). We use the Galois Connection (α, γ) as presented in Definition 1. Note that because the Galois Connection maps from the abstract lattice to the powerset of the (concrete) lattice, we assume all operators in the following are naturally extended to sets. Because as shown in Theorem 2 last # and time # are perfects abstractions of last and time, respectively, to show that lastTime # is a perfect abstraction of lastTime, we just need to show that for the cases where the composition of last # and time # is not a perfect abstraction of lastTime, lastTime # is now perfect. More precisely, the only case which is not perfect in the composition is the case, which means a gap occurred after the last event on the stream of values. Then lastTime # returns an interval from the timestamp of the last event on v to the timestamp at the end of the gap. This is the same which would be done when the concretization function first creates a set of streams containing an arbitrary amount of events in the gap which results in different timestamps as output. Either the latest from an event in the gap, if one exists or from the last event on v. The abstraction function would then creat said interval. In every other case, lastTime # outputs the same value as the composition of the perfect operators does.

D Data Abstraction of a Queue for Sliding Window
Average Computation

D.1 Concrete Queue
TeSSLa can handle complex data structures and corresponding functions. An ordered queue is a sequence of timestamps and values Q D = (T × D) * such that ∀ t 0 , d 0 , t 1 , d 1 , . . . , t n , d n ∈ Q D : t i−1 < t i where 1 ≤ i ≤ n We use the following functions on queues: fold (f, , acc, until) = acc
To improve the accuracy of the abstraction we insert the following limiting function as annotations on the possible ranges of variables into the concrete specification: In a similar fashion we add the a function removing newer elements from the queue which serves as annotation that a queue will never contain elements with a future timestamp: remNewer : T × Q D → Q D remNewer (t, q t 1 , d ) = remNewer (t, q) if t 1 > t q t 1 , d else remNewer (t, ) = Now we define abstract versions for the previously defined functions: remOlder # k (t, (u, q)) = (u, remOlder k (t, q)) if t − k < u (0, remOlder k (t, q)) else Note, that the abstract functions use intervals I # D to represent ranges of possible values.

D.3 Finite Abstract Queue
If we want to limit the queue to a certain number of events, we just need to change the enqueue function as follows: , (u, q), n) = enq # (t, d, (u, q)) if |q| < n enq c (t, d, (t 2 , t 2 q ), n) otherwise, with q = t 1 , d 1 , t 2 q For q ∈ Q # I # D we denote with |q| the number of data values in q.

D.4 Concrete Self-Updating Queue
We now update the specification to be self-updating, i.e., update automatically every time an event leaves the moving window completely. For this, we can use the operator delay to create additional events. We add the following equations to the original specification and use updated instead of queue and load in the computation of avg: timeout = lift(t, q → dataTimeout(q) − t + 5)(time(updated), updated) merged = merge(queue, last(updated, delay(timeout, load))) updated = lift(remOlder 5 )(time(merged), merged) where dataTimeout : Q D → T is defined as follows: dataTimeout( ) = ∞ dataTimeout( t, d ) = ∞ dataTimeout( t, d, t q) = t

D.5 Abstract Self-Updating Queue
The abstract version of the specification for the self-updating queue can again be derived by replacing all TeSSLa operators with their abstract counterparts: timeout = lift # (t, q → dataTimeout # (q) − t + 5)(time # (updated), updated) merged = merge # (queue, last # (updated, delay # (timeout, load))) updated = lift # (remOlder 5 )(time # (merged)), merged) where dataTimeout # : Q # I # D → T is defined as follows: The rest of the specification is adjusted to the abstract setting as shown before. The unrolling of last # and delay # as defined in Definition 2 and Definition 5 can be applied in order to realize this abstract TeSSLa specification in TeSSLa.