Skip to content

Commit

Permalink
feat: debug information when constraint fail (#29)
Browse files Browse the repository at this point in the history
* style: fix typo

* feat: wip! debug info

* feat: wip! debug info

* feat: debug info

* test: debug info when constraint fail

* chore: add pimo in ci
  • Loading branch information
adrienaury authored Sep 10, 2023
1 parent f3784e5 commit 9379b8a
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .devcontainer/Dockerfile.ci
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ RUN apk add --update --progress --no-cache make gomplate
ARG VERSION_MILLER=6.2.0
RUN wget -nv -O- https://github.com/johnkerl/miller/releases/download/v${VERSION_MILLER}/miller-${VERSION_MILLER}-linux-amd64.tar.gz | tar xz --strip-components 1 -C /usr/bin miller-${VERSION_MILLER}-linux-amd64/mlr \
&& chmod +x /usr/bin/mlr

ARG VERSION_PIMO=1.19.0
RUN wget -O- https://github.com/CGI-FR/PIMO/releases/download/v${VERSION_PIMO}/pimo_${VERSION_PIMO}_linux_amd64.tar.gz | tar xz -C /usr/bin pimo
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Types of changes

## [0.4.1]

- `Added` debug information in logs when a constraint fail.
- `Fixed` error handling, fatal errors will not print mimo help.
- `Fixed` all counters are now persisted (with persist option).

Expand Down
21 changes: 21 additions & 0 deletions cmd/mimo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"os"
"runtime"
"slices"
"sort"
"strings"

Expand Down Expand Up @@ -236,11 +237,31 @@ func appendColumnMetric(report mimo.Report, colname string, haserror bool) bool
Float64("rate-identifiable", metrics.Identifiant.Rate()).
Msg("summmary for column " + colname)
haserror = true

logSamples("coherence", "real-value", "pseudonyms", metrics.GetInvalidSamplesForCoherentRate(10)) //nolint:gomnd
logSamples("identifiant", "pseudonym", "real-values", metrics.GetInvalidSamplesForIdentifiantRate(10)) //nolint:gomnd
}

return haserror
}

func logSamples(target, labelForValue, labelForAssigned string, samples []mimo.Sample) {
for _, sample := range samples {
lenMax := fmt.Sprintf("%d", len(sample.AssignedValues))

if len(sample.AssignedValues) > 10 { //nolint:gomnd
sample.AssignedValues = sample.AssignedValues[:10]
}

slices.Sort(sample.AssignedValues)

log.Error().
Str(labelForValue, sample.OriginalValue).
Strs(labelForAssigned, sample.AssignedValues).
Msg("sample value that failed " + target + " because it was attributed " + lenMax + " " + labelForAssigned)
}
}

func initLog() {
color := false

Expand Down
2 changes: 1 addition & 1 deletion internal/infra/config_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func CreateConfig(yamlconfig *YAMLStructure) (mimo.Config, error) {
case "maskingRate":
constraint.Target = mimo.MaskingRate
case "coherentRate":
constraint.Target = mimo.CohenrentRate
constraint.Target = mimo.CoherentRate
case "identifiantRate":
constraint.Target = mimo.IdentifiantRate
default:
Expand Down
65 changes: 65 additions & 0 deletions internal/infra/pebble_multimap.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"os"
"strings"

"github.com/cgi-fr/mimo/pkg/mimo"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -153,6 +154,70 @@ func (b PebbleMultimapBackend) GetSize(key string) int {
return int(count)
}

func (b PebbleMultimapBackend) GetSamplesMulti(maxlen int) []mimo.Sample {
samples := []mimo.Sample{}

iter, _ := b.db.NewIter(b.prefixIterOptions([]byte(KeyPrefix)))

for valid := iter.First(); valid; valid = iter.Next() {
key := strings.TrimPrefix(string(iter.Key()), KeyPrefix)
array := map[string]int{}

_ = json.Unmarshal(iter.Value(), &array)

if len(array) > 1 {
assignedValues := []string{}

for assignedValue := range array {
assignedValues = append(assignedValues, assignedValue)
}

samples = append(samples, mimo.Sample{
OriginalValue: key,
AssignedValues: assignedValues,
})
}

if len(samples) == maxlen {
break
}
}

return samples
}

func (b PebbleMultimapBackend) GetSamplesMono(maxlen int) []mimo.Sample {
samples := []mimo.Sample{}

iter, _ := b.db.NewIter(b.prefixIterOptions([]byte(KeyPrefix)))

for valid := iter.First(); valid; valid = iter.Next() {
key := strings.TrimPrefix(string(iter.Key()), KeyPrefix)
array := map[string]int{}

_ = json.Unmarshal(iter.Value(), &array)

if len(array) == 1 {
assignedValues := []string{}

for assignedValue := range array {
assignedValues = append(assignedValues, assignedValue)
}

samples = append(samples, mimo.Sample{
OriginalValue: key,
AssignedValues: assignedValues,
})
}

if len(samples) == maxlen {
break
}
}

return samples
}

func (b PebbleMultimapBackend) NewSizeIterator() mimo.SizeIterator { //nolint: ireturn
iter, _ := b.db.NewIter(b.prefixIterOptions([]byte(CountPrefix)))

Expand Down
2 changes: 2 additions & 0 deletions pkg/mimo/driven.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type MultimapBackend interface {
SetKey(key string, value map[string]int) error
GetSize(key string) int
NewSizeIterator() SizeIterator
GetSamplesMono(n int) []Sample
GetSamplesMulti(n int) []Sample
}

type SizeIterator interface {
Expand Down
50 changes: 50 additions & 0 deletions pkg/mimo/in_memory_multimap.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,56 @@ func (m InMemoryMultimapBackend) GetSize(key string) int {
return len(m[key])
}

func (m InMemoryMultimapBackend) GetSamplesMulti(maxlen int) []Sample {
samples := []Sample{}

for value, array := range m {
if len(array) > 1 {
assignedValues := []string{}

for assignedValue := range array {
assignedValues = append(assignedValues, assignedValue)
}

samples = append(samples, Sample{
OriginalValue: value,
AssignedValues: assignedValues,
})
}

if len(samples) == maxlen {
break
}
}

return samples
}

func (m InMemoryMultimapBackend) GetSamplesMono(maxlen int) []Sample {
samples := []Sample{}

for value, array := range m {
if len(array) == 1 {
assignedValues := []string{}

for assignedValue := range array {
assignedValues = append(assignedValues, assignedValue)
}

samples = append(samples, Sample{
OriginalValue: value,
AssignedValues: assignedValues,
})
}

if len(samples) == maxlen {
break
}
}

return samples
}

// CountMin returns the minimum count of values associated to a key across the map.
func (m InMemoryMultimapBackend) NewSizeIterator() SizeIterator { //nolint: ireturn
sizes := []int{}
Expand Down
73 changes: 72 additions & 1 deletion pkg/mimo/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (m Metrics) CoherenceRateValidate() int {
result := 0

for _, constraint := range m.Constraints {
if constraint.Target == CohenrentRate {
if constraint.Target == CoherentRate {
if !validate(constraint.Type, constraint.Value, m.Coherence.Rate()) {
return -1
}
Expand Down Expand Up @@ -247,6 +247,72 @@ func (m Metrics) Validate() int {
return 0
}

// GetInvalidSamplesForCoherentRate will return at most n invalid sample if a constraint on coherent rate failed.
func (m Metrics) GetInvalidSamplesForCoherentRate(maxlen int) []Sample {
constraint := m.findFailedCoherentConstraint()
samples := []Sample{}

if constraint != nil {
if (constraint.Type == ShouldEqual && constraint.Value > m.Coherence.Rate()) ||
constraint.Type == ShouldBeGreaterThan || constraint.Type == ShouldBeGreaterThanOrEqualTo {
samples = append(samples, m.Coherence.Backend.GetSamplesMulti(maxlen)...)
}

if (constraint.Type == ShouldEqual && constraint.Value < m.Coherence.Rate()) ||
constraint.Type == ShouldBeLessThanOrEqualTo || constraint.Type == ShouldBeLowerThan {
samples = append(samples, m.Coherence.Backend.GetSamplesMono(maxlen)...)
}
}

return samples
}

func (m Metrics) findFailedCoherentConstraint() *Constraint {
for _, c := range m.Constraints {
c := c
if c.Target == CoherentRate {
if !validate(c.Type, c.Value, m.Coherence.Rate()) {
return &c
}
}
}

return nil
}

// GetInvalidSamplesForIdentifiantRate will return at most n invalid sample if a constraint on identifiant rate failed.
func (m Metrics) GetInvalidSamplesForIdentifiantRate(maxlen int) []Sample {
constraint := m.findFailedIdentifiantConstraint()
samples := []Sample{}

if constraint != nil {
if (constraint.Type == ShouldEqual && constraint.Value > m.Identifiant.Rate()) ||
constraint.Type == ShouldBeGreaterThan || constraint.Type == ShouldBeGreaterThanOrEqualTo {
samples = append(samples, m.Identifiant.Backend.GetSamplesMulti(maxlen)...)
}

if (constraint.Type == ShouldEqual && constraint.Value < m.Identifiant.Rate()) ||
constraint.Type == ShouldBeLessThanOrEqualTo || constraint.Type == ShouldBeLowerThan {
samples = append(samples, m.Identifiant.Backend.GetSamplesMono(maxlen)...)
}
}

return samples
}

func (m Metrics) findFailedIdentifiantConstraint() *Constraint {
for _, c := range m.Constraints {
c := c
if c.Target == IdentifiantRate {
if !validate(c.Type, c.Value, m.Identifiant.Rate()) {
return &c
}
}
}

return nil
}

type Report struct {
Metrics map[string]Metrics
subs Suscribers
Expand Down Expand Up @@ -451,3 +517,8 @@ func isExcluded(exclude []any, value any, valueStr string) bool {

return false
}

type Sample struct {
OriginalValue string
AssignedValues []string
}
2 changes: 1 addition & 1 deletion pkg/mimo/model_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type ConstraintTarget int

const (
MaskingRate ConstraintTarget = iota
CohenrentRate
CoherentRate
IdentifiantRate
)

Expand Down
8 changes: 8 additions & 0 deletions test/configs/config_debug_constraint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: "1"
metrics:
- name: "name"
constraints:
coherentRate:
shouldBeGreaterThan: 0
identifiantRate:
shouldBeGreaterThan: 0
40 changes: 40 additions & 0 deletions test/reports/report_debug_constraints_no.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>MIMO Report</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="MIMO Report" />
</head>
<body>
<h1>MIMO Report</h1>
<table border="1" cellspacing="0" cellpadding="5">
<thead>
<th>Field</th>
<th>Nil</th>
<th>Ignored</th>
<th>Masked</th>
<th>Missed</th>
<th>Masking Rate</th>
<th>Coherent Rate</th>
<th>Identifiable Rate</th>
<th>K</th>
</thead>
<tbody>

<tr>
<td>name</td>
<td>0</td>
<td>0</td>
<td>56</td>
<td>44</td>
<td style="background-color: orange">56.00 %</td>
<td style="background-color: orange">0.00 %</td>
<td style="background-color: orange">0.00 %</td>
<td style="background-color: lightgreen">2</td>
</tr>

</tbody>
</table>
</body>
</html>
40 changes: 40 additions & 0 deletions test/reports/report_debug_constraints_yes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>MIMO Report</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="MIMO Report" />
</head>
<body>
<h1>MIMO Report</h1>
<table border="1" cellspacing="0" cellpadding="5">
<thead>
<th>Field</th>
<th>Nil</th>
<th>Ignored</th>
<th>Masked</th>
<th>Missed</th>
<th>Masking Rate</th>
<th>Coherent Rate</th>
<th>Identifiable Rate</th>
<th>K</th>
</thead>
<tbody>

<tr>
<td>name</td>
<td>0</td>
<td>0</td>
<td>56</td>
<td>44</td>
<td style="background-color: orange">56.00 %</td>
<td style="background-color: red">0.00 %</td>
<td style="background-color: red">0.00 %</td>
<td style="background-color: lightgreen">2</td>
</tr>

</tbody>
</table>
</body>
</html>
Loading

0 comments on commit 9379b8a

Please sign in to comment.