# FSM Mapping

## Overview

This package implements a fast and efficient algorithm for generic glob style
string matching using a finite state machine (FSM).

### Source Hierachy

```
  '-- fsm
      '-- dump.go // functionality to dump the FSM to Dot file
      '-- formatter.go // format glob templates using captured * groups
      '-- fsm.go // manipulating and searching of FSM
      '-- minmax.go // min() max() function for interger
```

## FSM Explained

Per [Wikipedia](https://en.wikipedia.org/wiki/Finite-state_machine):

> A finite-state machine (FSM) or finite-state automaton (FSA, plural: automata),
> finite automaton, or simply a state machine, is a mathematical model of
> computation. It is an abstract machine that can be in exactly one of a finite
> number of states at any given time. The FSM can change from one state to
> another in response to some external inputs; the change from one state to
> another is called a transition. An FSM is defined by a list of its states, its
> initial state, and the conditions for each transition.

In our use case, each *state* is a substring after the input StatsD metric name is splitted by `.`.

### Add state to FSM

`func (f *FSM) AddState(match string, matchMetricType string,
maxPossibleTransitions int, result interface{}) int`

At first, the FSM only contains three states, representing three possible metric types:

           ____ [gauge]
          /
    (start)---- [counter]
          \
           '--- [ timer ]


Adding a rule `client.*.request.count` with type `counter` will make the FSM to be:


           ____ [gauge]
          /
    (start)---- [counter] -- [client] -- [*] -- [request] -- [count] -- {R1}
          \
           '--- [timer]

`{R1}` is short for result 1, which is the match result for `client.*.request.count`.

Adding a rule `client.*.*.size` with type `counter` will make the FSM to be:

           ____ [gauge]                      __ [request] -- [count] -- {R1}
          /                                 /
    (start)---- [counter] -- [client] -- [*]
          \                                  \__ [*] -- [size] -- {R2}
           '--- [timer]


### Finding a result state in FSM

`func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string)
(*mappingState, []string)`

For example, when mapping `client.aaa.request.count` with `counter` type in the
FSM, the `^1` to `^7` symbols indicate how FSM will traversal in its tree:


           ____ [gauge]                      __ [request] -- [count] -- {R1}
          /                                 /       ^5        ^6         ^7
    (start)---- [counter] -- [client] -- [*]
       ^1 \          ^2           ^3        \__ [*] -- [size] -- {R2}
           '--- [timer]                   ^4 


To map `client.bbb.request.size`, FSM will do a backtracking:


           ____ [gauge]                      __ [request] -- [count] -- {R1}
          /                                 /       ^5         ^6
    (start)---- [counter] -- [client] -- [*]
       ^1 \          ^2           ^3        \__ [*] -- [size] -- {R2}
           '--- [timer]                   ^4
                                                 ^7      ^8        ^9


## Debugging

To see all the states of the current FSM, use `func (f *FSM) DumpFSM(w io.Writer)`
to dump into a Dot file. The Dot file can be further renderer into image using:

```shell
$ dot -Tpng dump.dot > dump.png
```

In StatsD exporter, one could use the following:

```shell
$ statsd_exporter --statsd.mapping-config=statsd.rules --debug.dump-fsm=dump.dot
$ dot -Tpng dump.dot > dump.png
```

For example, the following rules:

```yaml
mappings:
- match: client.*.request.count
  name: request_count
  match_metric_type: counter
  labels:
    client: $1

- match: client.*.*.size
  name: sizes
  match_metric_type: counter
  labels:
    client: $1
    direction: $2
```

will be rendered as:

![FSM](fsm.png)

The `dot` program is part of [Graphviz](https://www.graphviz.org/) and is
available in most of popular operating systems.
