Skip to content

Fix orig edivisive flow - #141

Merged
henrikingo merged 6 commits into
apache:masterfrom
vishnuchalla:orig_edivisive
May 6, 2026
Merged

henrikingo merged 6 commits into
apache:masterfrom
vishnuchalla:orig_edivisive

Conversation

@vishnuchalla

Copy link
Copy Markdown
Contributor

Description

Fix orig edivsive flow. I am running into below error if more than 10 datapoints are being used for analysis

vchalla@vchalla-thinkpadp1gen2:~/myforks/otava$ otava --config-file /tmp/otava-10.yaml analyze tiny10 --last 15 -P 0.001 --window 15 -M 0.0 --orig-edivisive=true
INFO: Computing change points for test tiny10...
Traceback (most recent call last):
  File "/home/vchalla/.local/bin/otava", line 7, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/vchalla/myforks/otava/otava/main.py", line 651, in main
    script_main()
  File "/home/vchalla/myforks/otava/otava/main.py", line 578, in script_main
    analyzed_series = otava.analyze(
                      ^^^^^^^^^^^^^^
  File "/home/vchalla/myforks/otava/otava/main.py", line 125, in analyze
    produced_report = report.produce_report(test.name, report_type)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vchalla/myforks/otava/otava/report.py", line 52, in produce_report
    return self.__format_log_annotated(test_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vchalla/myforks/otava/otava/report.py", line 87, in __format_log_annotated
    change = [c for c in cp.changes if c.metric == col_name]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vchalla/myforks/otava/otava/report.py", line 87, in <listcomp>
    change = [c for c in cp.changes if c.metric == col_name]
                                       ^^^^^^^^
AttributeError: 'ChangePoint' object has no attribute 'metric'

Testing

Tested and verified in local

vchalla@vchalla-thinkpadp1gen2:~/myforks/otava$ otava --config-file /tmp/otava-15.yaml analyze tiny15 -P 0.001 --window 50 -M 0.0 --orig-edivisive
INFO: Computing change points for test tiny15...
time                       commit      metric1
-------------------------  --------  ---------
2026-01-01 00:00:00 +0000  c1              100
2026-01-02 00:00:00 +0000  c2              101
2026-01-03 00:00:00 +0000  c3               99
2026-01-04 00:00:00 +0000  c4              100
2026-01-05 00:00:00 +0000  c5              102
2026-01-06 00:00:00 +0000  c6              101
2026-01-07 00:00:00 +0000  c7              100
2026-01-08 00:00:00 +0000  c8               99
2026-01-09 00:00:00 +0000  c9              100
                                     ·········  
                                        +30.3%  
                                     ·········  
2026-01-10 00:00:00 +0000  c10             130
2026-01-11 00:00:00 +0000  c11             131
2026-01-12 00:00:00 +0000  c12             130
2026-01-13 00:00:00 +0000  c13             132
2026-01-14 00:00:00 +0000  c14             130

Signed-off-by: Vishnu Challa <vchalla@redhat.com>
@vishnuchalla

Copy link
Copy Markdown
Contributor Author

cc: @henrikingo @Gerrrr

@henrikingo

Copy link
Copy Markdown
Contributor

Unfortunately, rather than fixing the orig_edivisive flow, in master, we should actually remove it.

By orig_edivisive we mean the original, close to textbook implementation at MongoDB in 2017. Since we no longer want to depend on the external dependency, we cannot offer this option anymore.

Note that in the 0.7 branch you can still use --orig-edivisive, but as far as I can tell, this bug isn't happening in that branch.

@Gerrrr

Gerrrr commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Unfortunately, rather than fixing the orig_edivisive flow, in master, we should actually remove it.

+1.

Note that in the 0.7 branch you can still use --orig-edivisive, but as far as I can tell, this bug isn't happening in that branch.

@vishnuchalla if you find issues with --orig-edivisive in 0.7.0, we can fix it there and do a bugfix release.

@henrikingo

Copy link
Copy Markdown
Contributor

Closing. If we misunderstood something, please just reply here and reopen.

@henrikingo henrikingo closed this Mar 30, 2026
@henrikingo

Copy link
Copy Markdown
Contributor

Oh dear... My sincerest apologies. I was looking into something unrelated yesterday and realized that there is a completely valid --orig-edivisive flow also in the new algorithm in main branch / 0.8.0 and higher. I had forgotten that @Sowiks did re-implement a version of the algorithm that is entered at

def compute_change_points_orig(series: Sequence[SupportsFloat], max_pvalue: float = 0.001, seed: Optional[int] = None) -> Tuple[PermCPList, Optional[PermCPList]]:

...and your patch is against that.

I will re-open this and look at your patch later.

@henrikingo henrikingo reopened this Apr 30, 2026
@henrikingo

Copy link
Copy Markdown
Contributor

Ok yes,I caught up with this.

Working indepently on a different patch, I end up needing exactly this patch for --orig-edivisive

+1

@henrikingo

Copy link
Copy Markdown
Contributor

I think your patch looks good except for this one linting failure. If you move the TTestSignificanceTester one line up, I'll hit the button to rerun tests for you.

In the mean time, do you already have a signed contributor agreement on file with the ASF? If not:

https://www.apache.org/licenses/contributor-agreements.html
Short version is, download the ICLA pdf, sign it either with a pen or GnuPG, email it to secretary@apache.org

Signed-off-by: Vishnu Challa <vchalla@redhat.com>
@vishnuchalla

vishnuchalla commented May 4, 2026 •

Copy link
Copy Markdown
Contributor Author

I think your patch looks good except for this one linting failure. If you move the TTestSignificanceTester one line up, I'll hit the button to rerun tests for you.

In the mean time, do you already have a signed contributor agreement on file with the ASF? If not:

https://www.apache.org/licenses/contributor-agreements.html
Short version is, download the ICLA pdf, sign it either with a pen or GnuPG, email it to secretary@apache.org

@henrikingo Singed and emailed the ICLA. Also fixed the linting issue. PTAL once you get sometime. Thank you.

henrikingo added a commit that referenced this pull request May 4, 2026
The recent work in #96 to replace the original external dependency on the
so called "signal processing" repository with our own implementation,
introduced new classess ChangePoint and CandidateChangePoint, in
change_point_divisive/base.py but also left in place the original
ChangePoint class in analysis.py. These come together in series.py,
where the newer is renamed as _ChangePoint() and also acts as a parent
to older class, thus aligning their signature as much as possible.

It turns out having two similarly named classes can be a source of
confusion and bugs. For example, in #141  vishnuchalla fixes a bug
that is due to this and has essentially blocked the --orig-edivisive
code path completely.

This patch is an effort to make the existence of two separate
classes very explicit, by renaming them to ChagePointHunter
and ChangePointOtava based on their "lineage".

A test case is added to exercise the --orig-edivisive code path.
The test fails, as predicted by #141. The test is now cmmented out.
The bug is due to a missing cp.metric property in one variation
of the ChangePoint class.

Note that this patch is intended more for discussion than to merge.
@henrikingo

Copy link
Copy Markdown
Contributor

Whether testing the full output of otava analyze -h is meaningful is another question... But anyway, you need

--- a/tests/cli_help_test.py
+++ b/tests/cli_help_test.py

@@ -162,7 +162,7 @@ usage: otava analyze [-h] [--config-file CONFIG_FILE] [--graphite-url GRAPHITE_U
                      [--output {{log,json,regressions_only}}] [--branch [STRING]] [--metrics LIST]
 {usage_filter_lines}
                      [--last COUNT] [-P, --p-value PVALUE] [-M MAGNITUDE] [--window WINDOW]
-                     [--orig-edivisive ORIG_EDIVISIVE]
+                     [--orig-edivisive]
                      tests [tests ...]
 
-  --orig-edivisive ORIG_EDIVISIVE
-                        use the original edivisive algorithm with no windowing and weak change
+  --orig-edivisive      use the original edivisive algorithm with no windowing and weak change
                         points analysis improvements

@vishnuchalla

vishnuchalla commented May 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Whether testing the full output of otava analyze -h is meaningful is another question... But anyway, you need

--- a/tests/cli_help_test.py
+++ b/tests/cli_help_test.py

@@ -162,7 +162,7 @@ usage: otava analyze [-h] [--config-file CONFIG_FILE] [--graphite-url GRAPHITE_U
                      [--output {{log,json,regressions_only}}] [--branch [STRING]] [--metrics LIST]
 {usage_filter_lines}
                      [--last COUNT] [-P, --p-value PVALUE] [-M MAGNITUDE] [--window WINDOW]
-                     [--orig-edivisive ORIG_EDIVISIVE]
+                     [--orig-edivisive]
                      tests [tests ...]
 
-  --orig-edivisive ORIG_EDIVISIVE
-                        use the original edivisive algorithm with no windowing and weak change
+  --orig-edivisive      use the original edivisive algorithm with no windowing and weak change
                         points analysis improvements

I think the tests just need a re-run? Updated with this patch.

@henrikingo

Copy link
Copy Markdown
Contributor

The merge re-introduced 413d774

Signed-off-by: Vishnu Challa <vchalla@redhat.com>
@vishnuchalla

Copy link
Copy Markdown
Contributor Author

The merge re-introduced 413d774

Oops, fixed.

@henrikingo
henrikingo merged commit deb4bd1 into apache:master May 6, 2026
6 checks passed
@henrikingo

Copy link
Copy Markdown
Contributor

Thanks. And nice to have you here @vishnuchalla !

henrikingo added a commit that referenced this pull request Jun 1, 2026
The recent work in #96 to replace the original external dependency on the
so called "signal processing" repository with our own implementation,
introduced new classess ChangePoint and CandidateChangePoint, in
change_point_divisive/base.py but also left in place the original
ChangePoint class in analysis.py. These come together in series.py,
where the newer is renamed as _ChangePoint() and also acts as a parent
to older class, thus aligning their signature as much as possible.

It turns out having two similarly named classes can be a source of
confusion and bugs. For example, in #141  vishnuchalla fixes a bug
that is due to this and has essentially blocked the --orig-edivisive
code path completely.

This patch is an effort to make the existence of two separate
classes very explicit, by renaming them to ChagePointHunter
and ChangePointOtava based on their "lineage".

A test case is added to exercise the --orig-edivisive code path.
The test fails, as predicted by #141. The test is now cmmented out.
The bug is due to a missing cp.metric property in one variation
of the ChangePoint class.

Note that this patch is intended more for discussion than to merge.
Gerrrr added a commit that referenced this pull request Aug 9, 2026
* Rename the two different ChangePoint classes for clarity

The recent work in #96 to replace the original external dependency on the
so called "signal processing" repository with our own implementation,
introduced new classess ChangePoint and CandidateChangePoint, in
change_point_divisive/base.py but also left in place the original
ChangePoint class in analysis.py. These come together in series.py,
where the newer is renamed as _ChangePoint() and also acts as a parent
to older class, thus aligning their signature as much as possible.

It turns out having two similarly named classes can be a source of
confusion and bugs. For example, in #141  vishnuchalla fixes a bug
that is due to this and has essentially blocked the --orig-edivisive
code path completely.

This patch is an effort to make the existence of two separate
classes very explicit, by renaming them to ChagePointHunter
and ChangePointOtava based on their "lineage".

A test case is added to exercise the --orig-edivisive code path.
The test fails, as predicted by #141. The test is now cmmented out.
The bug is due to a missing cp.metric property in one variation
of the ChangePoint class.

Note that this patch is intended more for discussion than to merge.

* Unify the two ChangePoint classes and add container classes

* Unify the ChangePoint_ class in hunter code and the new
  ChangePoint introduced by the new edivisive implementation

Then it got out of hand a bit ...

* Separate index and timestamp into different domains.
  cp.index is used in the context of a single metric and its
  history of results. Time and commit otoh are on the
  ChangePointGroup level (essentially a "row"). Note
  that different metrics can now have different cp.index
  for the same cpg.time or cpg.attributes['commit'], if
  they have a different history.

* Introduce a ChangePoints class which is just a list of
  ChangePointGroups but actually comes with 2 different
  implementations.

The last one is supposed to become the class you are left holding
once all the change points are computed. Until now we had lots
of nice classes for each step of computation, but in the end
you were left holding a dict[str, ChangePointGroup]. The new
class now encapsulates that dict,

* Refactor common code between the significance testers

This moves stats and functionality up towards parent classes
so that generic stats like mean are always computed for
all variants. In fact TTestStat is now an empty class,
it's functionality fully absorbed by the parent. (But note
that the class name/type itself carries information about the pvalue.

* ruff/flake/idk

* format

* Re-enable tox alsso in gh actions

* fix: timezone in 3.10 compatible way

* bw compat: must use timezone.UTC

* Simplify __compute_change_points() by using ChangePoints earlier

* Simplify ChangePoints() constructor

Create separate copy(), from_dict(), from_list() constructors

Constructors now just create an empty container

Add tests for unified ChangePoint classes; fix many bugs

Also move BaseStats compute logic into a calculate() staticmethod
so copy() is a plain replace().

* Make ChangePoints._change_points private by adding _

* ChangePointsByMetric()[n] is not well defined, go via by_time()

Other small fixes

* Visit all change points and columns, not just values()[0]

* Add comment about why indexes will not be aligned or can have gaps

* TTestStats: Add tstatistic and degrees_of_freedom

Add a few t-test specific parameters to the TTestStats class

* Add to_json() serializers to subclasses of BaseStats

Note: No from_json(), as the higher level from_json() functions
can easily just call the constructor directly.

* Self review, various fixes

Added typing annotations and docstrings where I felt they were lacking.

Cleaned up the serialization and insert to bigquery and postgres. Both of which
continue to insert ChangePoint(s), even if they take a ChangePointGroup as
arguments. This is because .time and .attributes are now both a property of
the ChangePointGroup.

* Add pytest-cov for code coverage

* Add missing unit test coverage

In fact, went all the way and achieved 100% coverage in the
files mainly touched by this PR.

Lint and format edits too.

* review: Remove __eq__() for ChangePoint class

* review: PermutationStats.copy() wasn't copying NDArray deeply

* review: Fix and add test for ReportType.REGRESSIONS_ONLY

* review: datatype should be datetime

* review: to_json(): fix the case with multiple metrics

* review: Tests cleanup and fixes

* lint & format (tox)

* review: Add comment blocks to ChangePointsBy* classes

* fix: handle empty ChangePointsByMetric length

* fix: refresh change points by time after append

* fix: update weak change points after append

* fix: serialize weak change points from weak cache

* fix: restore analyzed series from grouped json

---------

Co-authored-by: Alex Sorokoumov <aleksandr.sorokoumov@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants