public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "André Erdmann" <dywi@mailerd.de>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/R_overlay:master commit in: roverlay/stats/
Date: Fri, 16 Aug 2013 10:43:11 +0000 (UTC)	[thread overview]
Message-ID: <1376648470.f400284d5fab7c5157796437fb6d0b47cde16072.dywi@gentoo> (raw)

commit:     f400284d5fab7c5157796437fb6d0b47cde16072
Author:     André Erdmann <dywi <AT> mailerd <DOT> de>
AuthorDate: Fri Aug 16 10:21:10 2013 +0000
Commit:     André Erdmann <dywi <AT> mailerd <DOT> de>
CommitDate: Fri Aug 16 10:21:10 2013 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=f400284d

roverlay/stats: rate stats

check stats for min/max values, get suggestions/notes

---
 roverlay/stats/dbcollector.py |  13 +-
 roverlay/stats/rating.py      | 372 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 377 insertions(+), 8 deletions(-)

diff --git a/roverlay/stats/dbcollector.py b/roverlay/stats/dbcollector.py
index a9169e6..2961aec 100644
--- a/roverlay/stats/dbcollector.py
+++ b/roverlay/stats/dbcollector.py
@@ -7,21 +7,18 @@
 import collections
 import weakref
 
+from . import rating
+
 class StatsDBCollector ( object ):
    VERSION = 0
 
-   # 'pc' := package count(er), 'ec' := ebuild _
-   NUMSTATS_KEYS = (
-      'pc_repo', 'pc_distmap', 'pc_filtered', 'pc_queued', 'pc_success',
-      'pc_fail', 'pc_fail_empty', 'pc_fail_dep', 'pc_fail_selfdep',
-      'pc_fail_err',
-      'ec_pre', 'ec_post', 'ec_written', 'ec_revbump',
-   )
+   NUMSTATS_KEYS = rating.NUMSTATS.keys()
    NUMSTATS = collections.namedtuple (
       "numstats", ' '.join ( NUMSTATS_KEYS )
    )
 
-   TIMESTATS_KEYS = ()
+
+   TIMESTATS_KEYS = rating.TIMESTATS.keys()
    TIMESTATS = collections.namedtuple (
       "timestats", ' '.join ( TIMESTATS_KEYS )
    )

diff --git a/roverlay/stats/rating.py b/roverlay/stats/rating.py
new file mode 100644
index 0000000..ec36002
--- /dev/null
+++ b/roverlay/stats/rating.py
@@ -0,0 +1,372 @@
+# R overlay -- stats collection, "rate" stats ("good","bad",...)
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import collections
+
+import roverlay.util.objects
+
+# 'pc' := package count(er), 'ec' := ebuild _
+#
+NUMSTATS = collections.OrderedDict ((
+   ( 'pc_repo',          "package files found in the repositories" ),
+   ( 'pc_distmap',       "package files in the mirror directory" ),
+   ( 'pc_filtered',      "packages ignored by package rules" ),
+   ( 'pc_queued',        "packages queued for ebuild creation" ),
+   ( 'pc_success',       "packages for which ebuild creation succeeded" ),
+   ( 'pc_fail',          "packages for which ebuild creation failed" ),
+   # actually, it's not the package that "fails"
+   ( 'pc_fail_empty',    "packages failed due to empty/unsuitable DESCRIPTION" ),
+   ( 'pc_fail_dep',      "packages failed due to unresolvable dependencies" ),
+   ( 'pc_fail_selfdep',  "packages failed due to unsatisfiable selfdeps" ),
+   ( 'pc_fail_err',      "packages failed due to unknown errors" ),
+   ( 'ec_pre',           "ebuild count prior to overlay creation" ),
+   ( 'ec_post',          "ebuild count after overlay creation" ),
+   ( 'ec_written',       "ebuilds written" ),
+   ( 'ec_revbump',       "ebuilds rev-bumped due to package file content change" ),
+))
+
+
+TIMESTATS = {}
+
+class StatsRating ( object ):
+
+   STATUS_NONE     = 0
+   ##STATUS_OK       = 2**1
+   STATUS_OK       = STATUS_NONE
+   STATUS_WARN     = 2**2
+   STATUS_ERR      = 2**3
+   STATUS_CRIT     = 2**4
+   STATUS_TOO_HIGH = 2**5
+   STATUS_TOO_LOW  = 2**6
+
+
+   STATUS_WARN_LOW  = STATUS_WARN | STATUS_TOO_LOW
+   STATUS_WARN_HIGH = STATUS_WARN | STATUS_TOO_HIGH
+   STATUS_ERR_LOW   = STATUS_ERR  | STATUS_TOO_LOW
+   STATUS_ERR_HIGH  = STATUS_ERR  | STATUS_TOO_HIGH
+   STATUS_CRIT_LOW  = STATUS_CRIT | STATUS_TOO_LOW
+   STATUS_CRIT_HIGH = STATUS_CRIT | STATUS_TOO_HIGH
+
+   STATUS_FAIL = ( ( 2**7 ) - 1 ) ^ STATUS_OK
+
+
+   def __init__ ( self, description ):
+      super ( StatsRating, self ).__init__()
+      self.description = description
+   # --- end of __init__ (...) ---
+
+   @roverlay.util.objects.abstractmethod
+   def get_rating ( self, value ):
+      return STATUS_NONE
+   # --- end of get_rating (...) ---
+
+# --- end of StatsRating ---
+
+class NumStatsCounterRating ( StatsRating ):
+
+   def __init__ ( self, description, value,
+      warn_high=None, err_high=None, crit_high=None,
+      warn_low=0, err_low=0, crit_low=0
+   ):
+      super ( NumStatsCounterRating, self ).__init__ ( description )
+      self.warn_high = warn_high
+      self.err_high  = err_high
+      self.crit_high = crit_high
+      self.warn_low  = warn_low
+      self.err_low   = err_low
+      self.crit_low  = crit_low
+      self.value     = value
+      self.status    = (
+         self.get_rating ( value ) if value is not None else None
+      )
+   # --- end of __init__ (...) ---
+
+   @classmethod
+   def new_fail_counter ( cls, description, value, warn=1, err=1, crit=1 ):
+      return cls ( description, value, warn, err, crit )
+   # --- end of new_fail_counter (...) ---
+
+   def get_rating ( self, value ):
+      too_high = lambda high, k: ( high is not None and k > high )
+      too_low  = lambda low,  k: ( low  is not None and k < low  )
+      ret = self.STATUS_NONE
+
+      if too_high ( self.warn_high, value ):
+         ret |= self.STATUS_WARN_HIGH
+
+      if too_low ( self.warn_low, value ):
+         ret |= self.STATUS_WARN_LOW
+
+      if too_high ( self.err_high, value ):
+         ret |= self.STATUS_ERR_HIGH
+
+      if too_low ( self.err_low, value ):
+         ret |= self.STATUS_ERR_LOW
+
+      if too_high ( self.crit_high, value ):
+         ret |= self.STATUS_CRIT_HIGH
+
+      if too_low ( self.crit_low, value ):
+         ret |= self.STATUS_CRIT_LOW
+
+      return self.STATUS_OK if ret == self.STATUS_NONE else ret
+   # --- end of get_rating (...) ---
+
+   def is_warning ( self ):
+      return self.status & self.STATUS_WARN
+   # --- end of is_warning (...) ---
+
+   def is_error ( self ):
+      return self.status & self.STATUS_ERR
+   # --- end of is_error (...) ---
+
+   def is_critical ( self ):
+      return self.status & self.STATUS_CRIT
+   # --- end of is_critical (...) ---
+
+   def is_ok ( self ):
+      return self.status == self.STATUS_OK
+   # --- end of is_ok (...) ---
+
+   def format_value ( self,
+      fmt_ok=None, fmt_warn=None, fmt_err=None, fmt_crit=None
+   ):
+      fmt = self.get_item ( fmt_ok, fmt_warn, fmt_err, fmt_crit )
+      if fmt:
+         return fmt.format ( str ( self.value ) )
+      elif fmt == "":
+         return fmt
+      else:
+         return str ( self.value )
+   # --- end of format_value (...) ---
+
+   def get_item ( self, item_ok, item_warn, item_err, item_crit ):
+      status = self.status
+      if self.status & self.STATUS_CRIT:
+         return item_crit
+      elif self.status & self.STATUS_ERR:
+         return item_err
+      elif self.status & self.STATUS_WARN:
+         return item_warn
+      else:
+         return item_ok
+   # --- end of get_item (...) ---
+
+   def get_word ( self,
+      word_ok="ok", word_warn="warn", word_err="err", word_crit="crit"
+   ):
+      return str ( self.get_item ( word_ok, word_warn, word_err, word_crit ) )
+   # --- end of get_word (...) ---
+
+
+# --- end of NumStatsCounterRating ---
+
+
+class NumStatsRating ( StatsRating ):
+
+   def __init__ ( self, values, description=None ):
+      assert isinstance ( values, dict )
+      super ( NumStatsRating, self ).__init__ ( description=description )
+      self.values = values
+      self.setup()
+   # --- end of __init__ (...) ---
+
+   @roverlay.util.objects.abstractmethod
+   def setup ( self ):
+      pass
+   # --- end of setup (...) ---
+
+   def get_rating ( self ):
+      return self
+   # --- end of get_rating (...) ---
+
+# --- end of NumStatsRating ---
+
+
+class RoverlayNumStatsRating ( NumStatsRating ):
+
+   ## COULDFIX: yield str key / int indizes in get_suggestions() and
+   ##           optionall "translate" them using a map
+   ## "event" => message
+   ##SUGGESTIONS = {}
+
+
+   def setup ( self ):
+      if __debug__:
+         assert set ( NUMSTATS.keys() ) == set ( self.values.keys() )
+
+      # FIXME: err/crit
+
+      values = self.values
+      v_ec_post = values['ec_post']
+      v_pc_repo = values['pc_repo']
+
+      # *_high=k -- warn/... if value > k
+      # *_low=k  -- warn/... if value < k
+      new_numstats = lambda key, **b: (
+         NumStatsCounterRating ( NUMSTATS[key], values[key], **b )
+      )
+
+      self.pc_repo = new_numstats ( 'pc_repo',
+         warn_low=1,
+         err_low=( 1 if values['pc_distmap'] > 0 else 0 ),
+      )
+
+      self.pc_distmap = new_numstats ( 'pc_distmap',
+         # src files of imported ebuilds don't get written to the distmap
+         #  (can be "fixed" with --distmap-verify)
+         warn_low=max ( 1, v_ec_post ),
+         err_low=( 1 if v_ec_post > 0 else 0 ),
+         warn_high=( 1.01*v_ec_post if v_ec_post > 0 else None ),
+         err_high=( 1.1*v_ec_post if v_ec_post > 0 else None ),
+      )
+
+      self.pc_filtered = new_numstats ( 'pc_filtered',
+         crit_high=( v_pc_repo - values['pc_queued'] ),
+      )
+
+      self.pc_queued = new_numstats ( 'pc_queued',
+         # cannot queue more packages than available
+         crit_high=( v_pc_repo - values['pc_filtered'] ),
+      )
+
+      self.pc_success = new_numstats ( 'pc_success',
+         crit_high=values['pc_queued'],
+         # warn about low pc_success/pc_queued ratio
+         warn_low=(
+            0.9*values['pc_queued'] if values['pc_queued'] > 0 else None
+         ),
+      )
+
+      self.pc_fail = new_numstats ( 'pc_fail',
+         # pc_queued would produce "false" warnings in incremental mode
+         warn_high=( max ( 0, 0.15 * v_pc_repo ) ),
+         err_high=(  max ( 0, 0.3  * v_pc_repo ) ),
+         crit_high=( max ( 0, 0.5  * v_pc_repo ) ),
+         crit_low=(
+            values['pc_fail_empty'] + values['pc_fail_dep']
+            + values['pc_fail_selfdep'] + values['pc_fail_err']
+         ),
+      )
+
+      self.pc_fail_empty   = new_numstats ( 'pc_fail_empty',
+         crit_high=values['pc_fail'],
+      )
+      self.pc_fail_dep     = new_numstats ( 'pc_fail_dep',
+         crit_high=values['pc_fail'],
+         warn_high=max ( 10, 0.01*values['pc_repo'] ),
+      )
+      self.pc_fail_selfdep = new_numstats ( 'pc_fail_selfdep',
+         crit_high=values['pc_fail'],
+      )
+      self.pc_fail_err     = new_numstats ( 'pc_fail_err',
+         warn_high=1, err_high=1, crit_high=values['pc_fail'],
+      )
+
+      self.ec_pre     = new_numstats ( 'ec_pre',
+         warn_high=v_ec_post,
+         err_high=max ( 0, 1.05*v_ec_post ),
+      )
+
+      self.ec_post    = new_numstats ( 'ec_post',
+         warn_low=values['ec_pre'],
+         # tolerate 5% ebuild loss (1/1.05 ~= 0.95)
+         err_low=max ( 1, 0.95*values['ec_pre'] ),
+      )
+      self.ec_written      = new_numstats ( 'ec_written',
+         err_low=values['pc_success'],
+      )
+      self.ec_revbump = new_numstats ( 'ec_revbump',
+         crit_high=min ( v_pc_repo, values['ec_pre'] ),
+         warn_high=min ( 1, 0.1*values['ec_pre'] ),
+      )
+   # --- end of setup (...) ---
+
+   def __iter__ ( self ):
+      for key in NUMSTATS.keys():
+         yield ( key, getattr ( self, key ) )
+   # --- end of __iter__ (...) ---
+
+   def get_suggestions ( self, pure_text=False ):
+      if pure_text:
+         code_format = { 'cstart': "\'", 'cend': "\'", }
+      else:
+         code_format = { 'cstart': "<code>", 'cend': "</code>", }
+
+      if not self.pc_repo.is_ok():
+         yield (
+            "low repo package count",
+            [ "check the repo config file", "drop repos without packages" ]
+         )
+
+      if not self.pc_distmap.is_ok():
+         details = [
+            'run {cstart}roverlay --distmap-verify{cend} to fix '
+            'the distmap'.format ( **code_format )
+         ]
+
+         if self.pc_distmap.status & self.STATUS_TOO_HIGH:
+            topic = "distmap file count is higher than the ebuild count"
+         else:
+            topic = "distmap file count is lower than the ebuild count"
+            details.append (
+               'run {cstart}roverlay --fixup-category-move[-reverse]{cend} '
+               ' after configuring relocations (in the package rules)'.format (
+                  **code_format
+               )
+            )
+         yield ( topic, details )
+
+
+      if self.pc_success.value < 1:
+         if self.pc_success.status & (self.STATUS_ERR|self.STATUS_CRIT):
+            yield ( "no ebuilds created", None )
+         else:
+            yield (
+               "no ebuilds created (not an issue in incremental mode)",
+               None
+            )
+      elif not self.pc_success.is_ok():
+         yield (
+            "only a few ebuilds created (not an issue in incremental mode)",
+            None
+         )
+
+      if self.pc_fail.value > 0 or not self.pc_success.is_ok():
+         details = []
+         if self.pc_fail_dep.value > 0:
+            details.append ( "write dependency rules" )
+         details.append ( 'configure package ignore rules' )
+
+         yield (
+            '{adj} failure rate'.format (
+               adj=self.pc_fail.get_word (
+                  "normal/expected", "moderate", "high", "very high"
+               )
+            ),
+            details
+         )
+
+
+
+      if not self.pc_fail_err.is_ok():
+         yield (
+            "failures due to unknown errors",
+            [ 'check the log files for python exceptions and report them', ]
+         )
+
+      if not self.ec_pre.is_ok() or not self.ec_post.is_ok():
+         yield ( "ebuild loss occurred (no suggestions available)", None )
+
+      if not self.ec_revbump.is_ok():
+         yield (
+            "unexpected ebuild revbump count (no suggestions available)",
+            None
+         )
+
+   # --- end of get_suggestions (...) ---
+
+# --- end of RoverlayNumStatsRating ---


             reply	other threads:[~2013-08-16 10:43 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-08-16 10:43 André Erdmann [this message]
  -- strict thread matches above, loose matches on Subject: below --
2014-02-21 17:36 [gentoo-commits] proj/R_overlay:master commit in: roverlay/stats/ André Erdmann
2013-09-05 10:24 André Erdmann
2013-09-05  9:25 André Erdmann
2013-09-05  9:25 André Erdmann
2013-08-16 12:42 André Erdmann
2013-08-15  9:18 André Erdmann
2013-08-02 10:34 André Erdmann
2013-07-29 14:56 André Erdmann
2013-07-29  8:55 André Erdmann
2013-07-26 13:02 André Erdmann
2013-07-25 15:20 André Erdmann
2013-07-25 14:28 André Erdmann
2013-07-24 16:52 André Erdmann

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1376648470.f400284d5fab7c5157796437fb6d0b47cde16072.dywi@gentoo \
    --to=dywi@mailerd.de \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox