#1156 frontend: add graph of actions
Merged 4 years ago by praiskup. Opened 4 years ago by thrnciar.
copr/ thrnciar/copr action-monitoring  into  master

@@ -582,7 +582,6 @@ 

          result = Munch()

          result.id = int(task_id)

          result.result = int(task_info['status'])

-         result.job_ended_on = time.time()

  

          try:

              self.frontend_client.update({"actions": [result]})

@@ -0,0 +1,23 @@ 

+ """

+ fill ended_on column in Action table

+ 

+ Revision ID: 2db1d0557b06

+ Revises: 4ed794df3bbb

+ Create Date: 2019-12-10 08:40:15.126168

+ """

+ 

+ import sqlalchemy as sa

+ from alembic import op

+ 

+ 

+ revision = '2db1d0557b06'

+ down_revision = '4ed794df3bbb'

+ 

+ def upgrade():

+     op.execute(

+         'UPDATE action SET ended_on = created_on + 5\

+          WHERE (result = 1 or result = 2) AND ended_on IS NULL'

+     )

+ 

+ def downgrade():

+     pass

@@ -0,0 +1,22 @@ 

+ """

+ add indexes to actions

+ 

+ Revision ID: a8ef299dcac8

+ Revises: cb928c34d36c

+ Create Date: 2020-01-06 14:04:30.362083

+ """

+ 

+ import sqlalchemy as sa

+ from alembic import op

+ 

+ 

+ revision = 'a8ef299dcac8'

+ down_revision = 'cb928c34d36c'

The two new revisions don't follow each other. Try: ./build_aux/check-alembic-revisions

./build_aux/check-alembic-revisions
...
a8ef299dcac8_add_indexes_to_actions.py
WARNING: dangling revision '2291ac900d95'

+ 

+ def upgrade():

+     op.create_index(op.f('ix_action_created_on'), 'action', ['created_on'], unique=False)

+     op.create_index(op.f('ix_action_ended_on'), 'action', ['ended_on'], unique=False)

+ 

+ def downgrade():

+     op.drop_index(op.f('ix_action_ended_on'), table_name='action')

+     op.drop_index(op.f('ix_action_created_on'), table_name='action')

@@ -0,0 +1,27 @@ 

+ """

+ add ActionsStatistics table

+ 

+ Revision ID: cb928c34d36c

+ Revises: 2db1d0557b06

+ Create Date: 2019-12-06 09:33:12.210295

+ """

+ 

+ import sqlalchemy as sa

+ from alembic import op

+ 

+ 

+ revision = 'cb928c34d36c'

+ down_revision = '2db1d0557b06'

+ 

+ def upgrade():

+     op.create_table('actions_statistics',

+     sa.Column('time', sa.Integer(), nullable=False),

+     sa.Column('stat_type', sa.Text(), nullable=False),

+     sa.Column('waiting', sa.Integer(), nullable=True),

+     sa.Column('success', sa.Integer(), nullable=True),

+     sa.Column('failed', sa.Integer(), nullable=True),

+     sa.PrimaryKeyConstraint('time', 'stat_type')

+     )

+ 

+ def downgrade():

+     op.drop_table('actions_statistics')

@@ -7,6 +7,8 @@ 

  from coprs import helpers

  from coprs import exceptions

  

+ from .helpers import get_graph_parameters

+ from sqlalchemy import and_, or_

  

  class ActionsLogic(object):

  
@@ -62,11 +64,13 @@ 

          Updates result, message and ended_on parameters.

          """

  

-         for attr in ["result", "message", "ended_on"]:

+         for attr in ["result", "message"]:

              value = upd_dict.get(attr, None)

              if value:

                  setattr(action, attr, value)

  

+             if attr == "result" and value in [BackendResultEnum("success"), BackendResultEnum("waiting")]:

Meh. There actually is even ActionResult.SUCCESS, etc. I'm lost why we have two enums, I think this is OK (we use BackendResultEnum for actions on other places).

+                 setattr(action, "ended_on", time.time())

          db.session.add(action)

  

      @classmethod
@@ -314,3 +318,89 @@ 

              created_on=int(time.time())

          )

          db.session.add(action)

+ 

+     @classmethod

+     def cache_action_graph_data(cls, type, time, waiting, success, failure):

+         result = models.ActionsStatistics.query\

+                 .filter(models.ActionsStatistics.stat_type == type)\

+                 .filter(models.ActionsStatistics.time == time).first()

+         if result:

+             return

+ 

+         try:

+             cached_data = models.ActionsStatistics(

+                 time = time,

+                 stat_type = type,

+                 waiting = waiting,

+                 success = success,

+                 failed = failure

+             )

+             db.session.add(cached_data)

+             db.session.commit()

+         except IntegrityError: # other process already calculated the graph data and cached it

+             db.session.rollback()

+ 

+     @classmethod

+     def get_actions_bucket(cls, start, end, actionType):

+         if actionType == 0:

+             # used for getting data for "processed" line of action graphs

+             result = models.Action.query\

+                 .filter(and_(

+                     models.Action.created_on <= end,

+                     or_(

+                         models.Action.ended_on > start,

+                         models.Action.ended_on == None

+                     )))\

+                 .count()

+             return result

+ 

+         else:

+             # used to getting data for "successed and failure" line of action graphs

+             result = models.Action.query\

+                 .filter(models.Action.ended_on <= end)\

+                 .filter(models.Action.ended_on > start)\

+                 .filter(models.Action.result == actionType)\

+                 .count()

+             return result

+ 

+     @classmethod

+     def get_cached_action_data(cls, params):

+         data = {

+             "waiting": [],

+             "success": [],

+             "failure": [],

+         }

+         result = models.ActionsStatistics.query\

+             .filter(models.ActionsStatistics.stat_type == params["type"])\

+             .filter(models.ActionsStatistics.time >= params["start"])\

+             .filter(models.ActionsStatistics.time <= params["end"])\

+             .order_by(models.ActionsStatistics.time)

+         for row in result:

+             data["waiting"].append(row.waiting)

+             data["success"].append(row.success)

+             data["failure"].append(row.failed)

+ 

+         return data

+ 

+     @classmethod

+     def get_action_graph_data(cls, type):

+         data = [["processed"], ["success"], ["failure"], ["time"] ]

+         params = get_graph_parameters(type)

+         cached_data = cls.get_cached_action_data(params)

+         for actionType in ["waiting", "success", "failure"]:

+             data[BackendResultEnum(actionType)].extend(cached_data[actionType])

+         for i in range(len(data[0]) - 1, params["steps"]):

+             step_start = params["start"] + i * params["step"]

+             step_end = step_start + params["step"]

+             waiting = cls.get_actions_bucket(step_start, step_end, BackendResultEnum("waiting"))

+             success = cls.get_actions_bucket(step_start, step_end, BackendResultEnum("success"))

+             failure = cls.get_actions_bucket(step_start, step_end, BackendResultEnum("failure"))

+             data[0].append(waiting)

+             data[1].append(success)

+             data[2].append(failure)

+             cls.cache_action_graph_data(type, time=step_start, waiting=waiting, success=success, failure=failure)

+ 

+         for i in range(params["start"], params["end"], params["step"]):

+             data[3].append(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(i)))

+ 

+         return data

@@ -34,6 +34,7 @@ 

  from .coprs_logic import MockChrootsLogic

  from coprs.logic.packages_logic import PackagesLogic

  

+ from .helpers import get_graph_parameters

  log = app.logger

  

  
@@ -190,7 +191,7 @@ 

      @classmethod

      def get_task_graph_data(cls, type):

          data = [["pending"], ["running"], ["avg running"], ["time"]]

-         params = cls.get_graph_parameters(type)

+         params = get_graph_parameters(type)

          cached_data = cls.get_cached_graph_data(params)

          data[0].extend(cached_data["pending"])

          data[1].extend(cached_data["running"])
@@ -218,7 +219,7 @@ 

      @classmethod

      def get_small_graph_data(cls, type):

          data = [[""]]

-         params = cls.get_graph_parameters(type)

+         params = get_graph_parameters(type)

          cached_data = cls.get_cached_graph_data(params)

          data[0].extend(cached_data["running"])

  
@@ -252,33 +253,6 @@ 

              db.session.rollback()

  

      @classmethod

-     def get_graph_parameters(cls, type):

-         if type is "10min":

-             # 24 hours with 10 minute intervals

-             step = 600

-             steps = 144

-         elif type is "30min":

-             # 24 hours with 30 minute intervals

-             step = 1800

-             steps = 48

-         elif type is "24h":

-             # 90 days with 24 hour intervals

-             step = 86400

-             steps = 90

- 

-         end = int(time.time())

-         end = end - (end % step) # align graph interval to a multiple of step

-         start = end - (steps * step)

- 

-         return {

-             "type": type,

-             "step": step,

-             "steps": steps,

-             "start": start,

-             "end": end,

-         }

- 

-     @classmethod

      def get_build_importing_queue(cls, background=None):

          """

          Returns Builds which are waiting to be uploaded to dist git

@@ -1,4 +1,5 @@ 

  # coding: utf-8

+ import time

  

  def slice_query(query, limit=100, offset=0):

      """
@@ -8,3 +9,29 @@ 

      :rtype: Query

      """

      return query.limit(limit).offset(offset)

+ 

+ def get_graph_parameters(type):

+     if type is "10min":

+         # 24 hours with 10 minute intervals

+         step = 600

+         steps = 144

+     elif type is "30min":

+         # 24 hours with 30 minute intervals

+         step = 1800

+         steps = 48

+     elif type is "24h":

+         # 90 days with 24 hour intervals

+         step = 86400

+         steps = 90

+ 

+     end = int(time.time())

+     end = end - (end % step) # align graph interval to a multiple of step

+     start = end - (steps * step)

+ 

+     return {

+         "type": type,

+         "step": step,

+         "steps": steps,

+         "start": start,

+         "end": end,

+     } 

\ No newline at end of file

@@ -1477,9 +1477,9 @@ 

      # optional message from the backend/whatever

      message = db.Column(db.Text)

      # time created as returned by int(time.time())

-     created_on = db.Column(db.Integer)

+     created_on = db.Column(db.Integer, index=True)

      # time ended as returned by int(time.time())

-     ended_on = db.Column(db.Integer)

+     ended_on = db.Column(db.Integer, index=True)

  

      def __str__(self):

          return self.__unicode__()
@@ -1657,3 +1657,10 @@ 

      stat_type = db.Column(db.Text, primary_key=True)

      running = db.Column(db.Integer)

      pending = db.Column(db.Integer)

+ 

+ class ActionsStatistics(db.Model):

+     time = db.Column(db.Integer, primary_key=True)

+     stat_type = db.Column(db.Text, primary_key=True)

+     waiting = db.Column(db.Integer)

+     success = db.Column(db.Integer)

+     failed = db.Column(db.Integer)

@@ -19,7 +19,7 @@ 

          },

          color: {pattern: colorPattern},

          data: {

-             hide: ['avg running'],

+             hide: ['avg running', 'success'],

              type: 'line',

              x: 'time',

              xFormat: '%Y-%m-%d %H:%M:%S'

@@ -32,6 +32,16 @@ 

        <div id="chartChrootsNinetyDays"></div>

      </div>

    </div>

+   <div class="row">

+     <h2>Actions</h2>

+     <p>Graph shows number of actions that were being processed in given time frame

+       and how many of them failed. There is also option to enable line for succeeded actions.

+     </p>

+     <h3>Actions during last 24 hours</h3>

+       <div id="chartActionsDay" class="line-chart-pf"></div>

+     <h3>Actions during last 90 days</h3>

+       <div id="chartActionsNinetyDays" class="line-chart-pf"></div>

+   </div>

  </div>

  {% endblock %}

  
@@ -42,6 +52,8 @@ 

  <script>

    lineGraph({{ data1|tojson }}, 25, "#chartDay", "%H:%M");

    lineGraph({{ data2|tojson }}, 15, "#chartNinetyDays", "%Y-%m-%d");

+   lineGraph({{ actions1|tojson }}, 25, "#chartActionsDay", "%H:%M");

+   lineGraph({{ actions2|tojson }}, 15, "#chartActionsNinetyDays", "%Y-%m-%d");

    chrootGraph({{ chroots1|tojson }}, "#chartChrootsDay");

    chrootGraph({{ chroots2|tojson }}, "#chartChrootsNinetyDays");

  </script>

@@ -45,9 +45,14 @@ 

      chroots_90d = builds_logic.BuildsLogic.get_chroot_histogram(curr_time - 90*86400, curr_time)

      data_24h = builds_logic.BuildsLogic.get_task_graph_data('10min')

      data_90d = builds_logic.BuildsLogic.get_task_graph_data('24h')

+     actions_24h = builds_logic.ActionsLogic.get_action_graph_data('10min')

+     actions_90d = builds_logic.ActionsLogic.get_action_graph_data('24h')

  

      return flask.render_template("status/stats.html",

                                   data1=data_24h,

                                   data2=data_90d,

                                   chroots1=chroots_24h,

-                                  chroots2=chroots_90d)

+                                  chroots2=chroots_90d,

+                                  actions1=actions_24h,

+                                  actions2=actions_90d

+                                  )

no initial comment

Metadata Update from @thrnciar:
- Pull-request tagged with: wip

4 years ago

1 new commit added

  • adds 24h and 90d graphs for actions
4 years ago

rebased onto 9298a4db8ead7793feec2453b164940452be2d4d

4 years ago

Metadata Update from @thrnciar:
- Pull-request untagged with: wip

4 years ago

I would move these methods from BuildsLogic to ActionsLogic.

And you're missing alembic migration script. Otherwise, the version before the merge commit looked good to me :)

Please rebase against master.

  • result.job_ended_on = time.time()
  • result.ended_on = time.time()

Setting ended_on on frontend side, (somewhere in /update/ route) would be
much better.

3 new commits added

  • Merge branch 'action-monitoring' of ssh://pagure.io/forks/thrnciar/copr/copr into action-monitoring
  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

rebased onto 1375151c90710764afd7fb06e244295b5ffa6fb3

4 years ago

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

rebased onto cb8c8663558348d7035ed4614823f999015e79ca

4 years ago

@praiskup, PTAL. I have updated the /update/ route.

This causes AttributeError: type object 'ActionsLogic' has no attribute 'get_graph_parameters'.
I'd move get_graph_parameters from BuildsLogic to helpers and use it from there.

You're missing an import for and_. Same for or_.

Seems like we actually need update of ActionsLogic.update_state_from_dict ... the /update/ route is (or can be) called in several situations, and not always we want to set 'ended_on'.

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

rebased onto 60a83bbb2aab7c73b597f3187c38443ddfa1e2b3

4 years ago

What if we only set ended_on if result is set in the dictionary, and the new result is actually one of the ended states? I still fear that we don't always want to set ended_on on update.... (one can e.g. want to update only the message, or so).

I also think you can drop the ended_on key from the iteration above...

rebased onto 212923e2450c8121f2415b6ec7858b02408e35ce

4 years ago

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

Please use constants for states, and use in []. The ended on should be removed from iteration

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

rebased onto 148adb61fa111a2a80ab6a68908c5df82a1de8b5

4 years ago

Very good job, @thrnciar, thank you! Some notes:
- the first non-cached run takes extraordinary long (the initial stats page load took 1:30m)
- the processed/successful lines are (almost) duplicates, I'd keep only one of them to be honest rather than repeatedly explain why they are that much similar :-), or at least keep one line disabled by default
- the graphs should somehow explain what they mean .... we should some description paragraph saying what actions acrtually are, and how they differ form build jobs ... etc.

Metadata Update from @praiskup:
- Pull-request tagged with: needs-work

4 years ago

5 new commits added

  • Merge branch 'action-monitoring' of ssh://pagure.io/forks/thrnciar/copr/copr into action-monitoring
  • adds 24h and 90d graphs for actions
  • frontend: don't traceback for invalid copr:// repos
  • typo
  • frontend: removes unnecessary imports of flask-script
4 years ago

rebased onto f3afb735646d44d538e7965e387f057ed4ea1a0f

4 years ago

1 new commit added

  • adds 24h and 90d graphs for actions
4 years ago

1 new commit added

  • backend: fixes not saving end time of actions
4 years ago

rebased onto c9ffee4f7af594fabf27851734e2c7068716e294

4 years ago

rebased onto 269457589e990dd25cddeb2200470646a9d6093d

4 years ago

rebased onto 677725f350cc78bfb147c817b347a226e018a180

4 years ago

Metadata Update from @thrnciar:
- Pull-request untagged with: needs-work

4 years ago

The two new revisions don't follow each other. Try: ./build_aux/check-alembic-revisions

./build_aux/check-alembic-revisions
...
a8ef299dcac8_add_indexes_to_actions.py
WARNING: dangling revision '2291ac900d95'

Can you please move ActionResult from backend/backend/actions.py to common
enums.py?, Then we can add

    def __getattr__(self, name):
        return self.vals[name]

to EnumType class... and use just ActionResult.WAITING as in backend.

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago
for type in ["waiting", "success", "failed"]: 
   data[BuildResultEnum(type)].extend(cached_data[type])

nice!, just please document that this is for "processed" part of graph

It would be nice to have this on one place, to not duplicate the same sentence. The text is also a bit misleading, I'd say that "processed" means "number of actions that backend was working on during the time frime" (or sth. like that).

As discussed f2f, we should also move the new graphs below all the other graphs related to builds.

2 new commits added

  • adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

2 new commits added

  • frontend: adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

2 new commits added

  • frontend: adds 24h and 90d graphs for actions
  • backend: fixes not saving end time of actions
4 years ago

Meh. There actually is even ActionResult.SUCCESS, etc. I'm lost why we have two enums, I think this is OK (we use BackendResultEnum for actions on other places).

rebased onto b899e4e

4 years ago

Pull-Request has been merged by praiskup

4 years ago
Metadata
Flags
Copr build
pending (50%)
#1141296
4 years ago
Copr build
success (100%)
#1141295
4 years ago
Copr build
success (100%)
#1139300
4 years ago
Copr build
success (100%)
#1139299
4 years ago
Copr build
success (100%)
#1139295
4 years ago
Copr build
success (100%)
#1139294
4 years ago
Copr build
success (100%)
#1139187
4 years ago
Copr build
success (100%)
#1139186
4 years ago
Copr build
failure
#1139185
4 years ago
Copr build
failure
#1139184
4 years ago
Copr build
failure
#1139183
4 years ago
Copr build
failure
#1139182
4 years ago
Copr build
failure
#1139181
4 years ago
Copr build
failure
#1139180
4 years ago
Copr build
failure
#1139179
4 years ago
Copr build
success (100%)
#1131534
4 years ago
Copr build
success (100%)
#1131532
4 years ago
Copr build
success (100%)
#1131437
4 years ago
Copr build
success (100%)
#1131436
4 years ago
Copr build
success (100%)
#1130844
4 years ago
Copr build
success (100%)
#1130843
4 years ago
Copr build
success (100%)
#1130842
4 years ago
Copr build
success (100%)
#1130841
4 years ago
Copr build
success (100%)
#1129874
4 years ago
Copr build
success (100%)
#1129873
4 years ago
Copr build
success (100%)
#1129869
4 years ago
Copr build
success (100%)
#1129868
4 years ago
Copr build
success (100%)
#1129867
4 years ago
Copr build
success (100%)
#1129866
4 years ago
Copr build
success (100%)
#1129640
4 years ago
Copr build
pending (50%)
#1129639
4 years ago
Copr build
success (100%)
#1129311
4 years ago
Copr build
success (100%)
#1129310
4 years ago
Copr build
success (100%)
#1129307
4 years ago
Copr build
success (100%)
#1129306
4 years ago
Copr build
success (100%)
#1129300
4 years ago
Copr build
success (100%)
#1129299
4 years ago
Copr build
success (100%)
#1129295
4 years ago
Copr build
success (100%)
#1129294
4 years ago
Copr build
success (100%)
#1128408
4 years ago
Copr build
success (100%)
#1128404
4 years ago
Copr build
success (100%)
#1128403
4 years ago
Copr build
success (100%)
#1127269
4 years ago
Copr build
success (100%)
#1127268
4 years ago