Leif Ryge avatar Leif Ryge committed 4e137c4 Merge

merge from default

Comments (0)

Files changed (21)

+24.2013.04.16.20.23.30-54ee619aab20
+
+* add three new methods to REST API: CheckMetric{MeanValue,Rate,SampleRate}
+* remove paritally supported browsers
+* make event collector task try resending rotate log messages if it looks like it got dropped
 * fix typo
 
 23.2013.04.08.21.34.59-5a37a3fa035d
-23.2013.04.08.21.34.59-5a37a3fa035d-dev
+24.2013.04.16.20.23.30-54ee619aab20-dev
+Upgrading from 24.2013.04.16.20.23.30-54ee619aab20
+  * No special upgrade steps required
+
 Upgrading from 23.2013.04.08.21.34.59-5a37a3fa035d
   * No special upgrade steps required
 

client_package/ChangeLog

+8.2013.04.23.22.11.52-a36f144fddb5
+
+  * Made data-tool checkmetric not pretty-print output
+  * Add documentation about nagios/icinga
+
+8.2013.04.17.01.40.45-39a67fc85995
+
+  * made data-tool checkmetric exit with status 2 if not OK
+
+8.2013.04.17.00.39.39-73c0d969a1bb
+
   * Chage min resolution in eventsink tool to 1s from 6s
+  * add sbin/data-tool with new checkmetric method
 
 7.2013.03.23.00.34.00-729f02862e3e
 

client_package/RELEASE_VERSION

-7.2013.03.23.00.34.00-729f02862e3e-dev
+8.2013.04.23.22.11.52-a36f144fddb5-dev

client_package/UPGRADING

+Upgrading from 8.2013.04.23.22.11.52-a36f144fddb5
+  * No special upgrade steps required
+
+Upgrading from 8.2013.04.17.01.40.45-39a67fc85995
+  * No special upgrade steps required
+
+Upgrading from 8.2013.04.17.00.39.39-73c0d969a1bb
+  * No special upgrade steps required
+
 Upgrading from 7.2013.03.23.00.34.00-729f02862e3e
   * No special upgrade steps required
 

client_package/debian/changelog

-statmoverclient (7.2013.03.23.00.34.00-729f02862e3e-dev) oneiric; urgency=low
+statmoverclient (8.2013.04.23.22.11.52-a36f144fddb5-dev) precise; urgency=low
 
   * Development build
 
- -- Leif Ryge <leif@statmover.com>  Fri, 22 Mar 2013 17:34:09 -0700
+ -- user <user@statmover.com>  Tue, 23 Apr 2013 15:12:14 -0700
+
+statmoverclient (8.2013.04.23.22.11.52-a36f144fddb5) precise; urgency=low
+
+  * Made data-tool checkmetric not pretty-print output
+  * Add documentation about nagios/icinga
+
+ -- Leif Ryge <leif@statmover.com>  Tue, 23 Apr 2013 15:11:54 -0700
+
+statmoverclient (8.2013.04.17.01.40.45-39a67fc85995) precise; urgency=low
+
+  * made data-tool checkmetric exit with status 2 if not OK 
+
+ -- Leif Ryge <leif@statmover.com>  Tue, 16 Apr 2013 18:40:47 -0700
+
+statmoverclient (8.2013.04.17.00.39.39-73c0d969a1bb) precise; urgency=low
+
+  * Chage min resolution in eventsink tool to 1s from 6s
+  * add sbin/data-tool with new checkmetric method
+
+ -- Leif Ryge <leif@statmover.com>  Tue, 16 Apr 2013 17:39:41 -0700
 
 statmoverclient (7.2013.03.23.00.34.00-729f02862e3e) oneiric; urgency=low
 

client_package/setup.py

     url              = 'https://statmover.com/',
     scripts          = [ 
         'sbin/emit-client',
+        'sbin/data-tool',
         'sbin/component-tool',
         'sbin/eventsink-tool',
         ],
-saturnalia (23.2013.04.08.21.34.59-5a37a3fa035d-dev) precise; urgency=low
+saturnalia (24.2013.04.16.20.23.30-54ee619aab20-dev) precise; urgency=low
 
   * Development build
 
- -- user <user@statmover.com>  Mon, 08 Apr 2013 14:36:00 -0700
+ -- user <user@statmover.com>  Tue, 16 Apr 2013 13:24:15 -0700
+
+saturnalia (24.2013.04.16.20.23.30-54ee619aab20) precise; urgency=low
+
+  * add three new methods to REST API: CheckMetric{MeanValue,Rate,SampleRate}
+  * remove paritally supported browsers
+  * make event collector task try resending rotate log messages if it looks like it got dropped
+  * fix typo
+
+ -- Leif Ryge <leif@statmover.com>  Tue, 16 Apr 2013 13:23:33 -0700
 
 saturnalia (23.2013.04.08.21.34.59-5a37a3fa035d) precise; urgency=low
 

src/js/saturnalia_console.js

             } ;
 
             var partial = {
-              Firefox : true ,
-              Safari  : true ,
+              //Firefox : true ,
+              //Safari  : true ,
             } ;
 
             

src/python/saturnalia/eventcollectortask.pyx

 
     WINDOW_STAT_INTERVAL = 10 * 1000
 
+    TRY_ROTATE_TIMEOUT = 5 * 1000
 
     @staticmethod
     def defaultFactory ( config, logger, taskRouter ):
         self._writerPid          = None
         self._pruneTime          = None
         self._nextChunkWriteTime = util.getTimeInMs()
+        self._tryRotateTime      = None
         self._accpatorHandle     = None
 
 
                 self._rotateLogId = logId
 
             elif logId != self._rotateLogId:
+                self._tryRotateTime = None
                 self._startChunkWrite( handleUsed )
 
-            elif self._nextChunkWriteTime != None and now > self._nextChunkWriteTime:
-                self._nextChunkWriteTime = None
+            else:
+                chunkWriteTime = self._nextChunkWriteTime is not None \
+                                 and now > self._nextChunkWriteTime
 
-                tag  = ROTATE_LOG_TAG
-                data = {}
-                self._sendToAccptor ( tag, data, handleUsed )
+                rotateTimeOut = self._tryRotateTime is not None \
+                                and ( now - self._tryRotateTime ) > self.TRY_ROTATE_TIMEOUT
+
+                if rotateTimeOut == True:
+                    self._logger.info( "Missed TRY_ROTATE_TIMEOUT by %d ms", now - self._tryRotateTime )
+
+                if chunkWriteTime == True or rotateTimeOut == True :
+                    self._nextChunkWriteTime = None
+                    self._tryRotateTime      = now
+                
+                    tag  = ROTATE_LOG_TAG
+                    data = {}
+                    self._sendToAccptor ( tag, data, handleUsed )
 
         
 

src/python/saturnalia/messagebuilder.pyx

      EmitRateStatWindow                                , \
      ProvisionRateStatWindow                           , \
      SendQuery                                         , \
+     CheckMetricMeanValue                              , \
+     CheckMetricMeanValueResponse                      , \
+     CheckMetricRateMs                                 , \
+     CheckMetricRateMsResponse                         , \
+     CheckMetricSampleRateMs                           , \
+     CheckMetricSampleRateMsResponse                   , \
+     CheckMetricError                                  , \
      WebQueryResponse                                  , \
      HostPortPair                                      , \
      ServiceRegistration                               , \
         Query,
         SendQuery,
         WebQueryResponse,
+        CheckMetricMeanValue,
+        CheckMetricMeanValueResponse,
+        CheckMetricRateMs,
+        CheckMetricRateMsResponse,
+        CheckMetricSampleRateMs,
+        CheckMetricSampleRateMsResponse,
+        CheckMetricError,
         DataServerAddEventSink,
         DataServerHasEventSink,
         DataServerHasEventSinkResponse,

src/python/saturnalia/messagetypes.py

         )
 
 
+class CheckMetricMeanValue ( MessageDataType ):
+
+    def __init__ ( self ):
+        MessageDataType.__init__( self,
+            (
+                Field( 'tenant'     , TenantId ),        
+                Field( 'name'       , String   ),
+                Field( 'start_time' , Int64    ),
+                Field( 'end_time'   , Int64    ),
+                Field( 'min'        , Float    ),
+                Field( 'max'        , Float    ),
+            )
+        )
+
+class CheckMetricRateMs       ( CheckMetricMeanValue ): pass
+class CheckMetricSampleRateMs ( CheckMetricMeanValue ): pass
+
+
+class CheckMetricMeanValueResponse ( MessageDataType ):
+
+    def __init__ ( self ):
+        MessageDataType.__init__( self,
+            (
+                Field   ( 'request_id' , Int64 ),
+                Field   ( 'value'      , Int64 ),
+                Field   ( 'scale'      , Int64 ),
+                Field   ( 'result'     , Int32 ),
+            )
+        )
+
+CheckMetricMeanValueResponse.PASS     = 1
+CheckMetricMeanValueResponse.FAIL_MAX = 2
+CheckMetricMeanValueResponse.FAIL_MIN = 3
+
+class CheckMetricRateMsResponse       ( CheckMetricMeanValueResponse ): pass
+class CheckMetricSampleRateMsResponse ( CheckMetricMeanValueResponse ): pass
+
+
+class CheckMetricError ( MessageDataType ):
+
+    def __init__ ( self ):
+        MessageDataType.__init__( self,
+            (
+                Field   ( 'request_id' , Int64 ),
+                Field   ( 'type'       , Int32 ),
+            )
+        )
+
+CheckMetricError.UNKNOWN_NAME   = 0
+CheckMetricError.INTERNAL_ERROR = 1
+CheckMetricError.NO_DATA        = 2
+
+
 class HostPortPair ( MessageDataType ):
 
     def __init__ ( self ):

src/python/saturnalia/sweb.py

 import sys
 
 # our imports
-from saturnalia.web.tasks.proxytask       import WebProxyTask
-from saturnalia.web.tasks.datarequesttask import WebDataRequestTask
-from saturnalia.swebconfig                import SwebConfig
-from saturnalia.eventroutertask           import EventRouterTask
-from saturnalia.provisionertask           import ProvisionerTask
-from saturnalia.accountprovisionertask    import AccountProvisionerTask
-from saturnalia.tenantservicesproxytask   import TenantServicesProxyTask
+from saturnalia.web.tasks.proxytask             import WebProxyTask
+from saturnalia.web.tasks.datarequesttask       import WebDataRequestTask
+from saturnalia.web.tasks.checkmetrictask       import CheckMetricTask
+from saturnalia.swebconfig                      import SwebConfig
+from saturnalia.eventroutertask                 import EventRouterTask
+from saturnalia.provisionertask                 import ProvisionerTask
+from saturnalia.accountprovisionertask          import AccountProvisionerTask
+from saturnalia.tenantservicesproxytask         import TenantServicesProxyTask
 
 from saturnalia.messagetypes import \
      AccountManagerAuthenticateUser            , \
      Query                                     , \
      RouteDbRouteAdd                           , \
      RouteDbRouteQuery                         , \
+     CheckMetricMeanValue                      , \
+     CheckMetricRateMs                         , \
+     CheckMetricSampleRateMs                   , \
      SendQuery
 
 # task imports
             ROUTE_MESSAGES : [ SendQuery ]                     ,
             },
         {
+            TASK_FACTORY   : CheckMetricTask.defaultFactory ,
+            FACTORY_KEY    : TWISTED_FACTORY                     ,
+            ROUTE_MESSAGES : [
+                CheckMetricMeanValue    ,
+                CheckMetricRateMs       ,
+                CheckMetricSampleRateMs ,
+                ],
+            },
+        {
             TASK_FACTORY   : WebProxyTask.defaultFactory ,
             FACTORY_KEY    : TWISTED_FACTORY             ,
             ROUTE_MESSAGES : [ ]                         ,

src/python/saturnalia/web/resources/rest.py

                                     ProvisionEventSinkRequest2             , \
                                     ProvisionEventSinkResponse2            , \
                                     SendQuery                              , \
+                                    CheckMetricMeanValue                   , \
+                                    CheckMetricMeanValueResponse           , \
+                                    CheckMetricRateMs                      , \
+                                    CheckMetricRateMsResponse              , \
+                                    CheckMetricSampleRateMs                , \
+                                    CheckMetricSampleRateMsResponse        , \
+                                    CheckMetricError                       , \
                                     WebQueryResponse
 
 
 ProvisionEventSinkRequestTagJson      = convertUInt64ToHex( ProvisionEventSinkRequest      ().getMessageTag() )
 ProvisionEventSinkRequest2TagJson     = convertUInt64ToHex( ProvisionEventSinkRequest2     ().getMessageTag() )
 SendQueryTagJson                      = convertUInt64ToHex( SendQuery                      ().getMessageTag() )
+CheckMetricMeanValueTagJson           = convertUInt64ToHex( CheckMetricMeanValue           ().getMessageTag() )
+CheckMetricRateMsTagJson              = convertUInt64ToHex( CheckMetricRateMs              ().getMessageTag() )
+CheckMetricSampleRateMsTagJson        = convertUInt64ToHex( CheckMetricSampleRateMs        ().getMessageTag() )
 
 
+CHECK_MEAN_VALUE_RESPONSE_STRINGS = {
+    CheckMetricMeanValueResponse.PASS     : "OK"            ,
+    CheckMetricMeanValueResponse.FAIL_MIN : "below minimum" ,
+    CheckMetricMeanValueResponse.FAIL_MAX : "above maximum" ,
+    }
+
+CHECK_RATE_MS_RESPONSE_STRINGS = {
+    CheckMetricRateMsResponse.PASS     : "OK"            ,
+    CheckMetricRateMsResponse.FAIL_MIN : "below minimum" ,
+    CheckMetricRateMsResponse.FAIL_MAX : "above maximum" ,
+    }
+
+CHECK_SAMPLE_RATE_MS_RESPONSE_STRINGS = {
+    CheckMetricSampleRateMsResponse.PASS     : "OK"            ,
+    CheckMetricSampleRateMsResponse.FAIL_MIN : "below minimum" ,
+    CheckMetricSampleRateMsResponse.FAIL_MAX : "above maximum" ,
+    }
+
+CHECK_VALUE_ERROR_STRINGS = {
+    CheckMetricError.UNKNOWN_NAME   : "unknown name"   ,
+    CheckMetricError.INTERNAL_ERROR : "internal error" ,
+    CheckMetricError.NO_DATA        : "no data"        ,
+    }
+
 class RestResources ( resource.Resource ):
     isLeaf = False
 
             'ComponentDbGetByNameAndType'     : self._parseComponentDbGetByNameAndType     ,
             'ComponentDbQueryByNameSubstring' : self._parseComponentDbQueryByNameSubstring ,
             'Query'                           : self._parseQuery                           ,
+            'CheckMetricMeanValue'            : self._parseCheckMetricMeanValue            ,
+            'CheckMetricRate'                 : self._parseCheckMetricRate                 ,
+            'CheckMetricSampleRate'           : self._parseCheckMetricSampleRate           ,
             }
 
         serializerMap = {
             ComponentDbResults                     : self._serializeComponentDbResults                     ,
             ComponentDbQueryByKeySubstringResponse : self._serializeComponentDbQueryByKeySubstringResponse ,
             WebQueryResponse                       : self._serializeWebQueryResponse                       ,
+            CheckMetricMeanValueResponse           : self._serializeCheckMetricMeanValueResponse           ,
+            CheckMetricRateMsResponse              : self._serializeCheckMetricRateMsResponse              ,
+            CheckMetricSampleRateMsResponse        : self._serializeCheckMetricSampleRateMsResponse        ,
+            CheckMetricError                       : self._serializeCheckMetricError                       ,
             }
 
         self._serializers = {}
             messageId = self._task.getNextMessageId()
 
             deadline = int( time.time() * 1000 ) + self.REQUEST_TIMEOUT
-
-            message = self._messageBuilder.makeMessageFromJsonData( data, senderHandle, messageId, deadline, jsonTag )
+            
+            try:
+                message = self._messageBuilder.makeMessageFromJsonData( data, senderHandle, messageId, deadline, jsonTag )
+            except Exception, ex:
+                raise Exception( (ex, data ) ) #BOOG
 
             action = TaskAction.ROUTE
 
         return ( SendQueryTagJson, data )
 
 
+    def _parseCheckMetricWorker ( self, payload, tenant, tag, convertRatePerSecondToRateToMs ):
+
+        requestFields = ( 'name', 'min', 'max', 'interval' )
+
+        name, min, max, interval = self._getRequestFields( payload, requestFields )
+
+        min      = float( min )
+        max      = float( max )
+        interval = int( interval )
+
+        endTime   = util.getTimeInMs()
+        startTime = endTime - interval
+
+        # BUG: maybe we should try to honor maxQuerySlots here?
+
+        if max < min:
+            raise MalformedRequest( "min must be less than or equal to max" )
+
+        if convertRatePerSecondToRateToMs:
+            min /= 1000
+            max /= 1000
+
+        data = {
+            'tenant'     : tenant     ,
+            'name'       : name       ,
+            'start_time' : startTime  ,
+            'end_time'   : endTime    ,
+            'min'        : min        ,
+            'max'        : max        ,
+            }
+
+        return ( tag, data )
+
+
+    def _parseCheckMetricMeanValue ( self, payload, tenant ):
+        return self._parseCheckMetricWorker ( payload, tenant, CheckMetricMeanValueTagJson, False )
+
+
+    def _parseCheckMetricRate ( self, payload, tenant ):
+        return self._parseCheckMetricWorker ( payload, tenant, CheckMetricRateMsTagJson, True )
+
+
+    def _parseCheckMetricSampleRate ( self, payload, tenant ):
+        return self._parseCheckMetricWorker ( payload, tenant, CheckMetricSampleRateMsTagJson, True )
+
+
     # Serializers:
     def _serializeProvisionEventSinkResponse ( self, payloadInstance ):
 
         return ( responseName, payload )
 
 
+    def _serializeCheckMetricMeanValueResponse ( self, payloadInstance ):
+
+        # BUG: We should verify request_id for defense-in-depth / belt-and-suspenders.  How?
+
+        responseName = 'CheckMetricMeanValueResponse'
+
+        value, scale, result = map(int, payloadInstance.readFields( ( 'value', 'scale', 'result' ) ) )
+
+        value *= 10**scale
+
+        payload = dict( value=value, result=CHECK_MEAN_VALUE_RESPONSE_STRINGS[result] )
+
+        return ( responseName, payload )
+
+
+    def _serializeCheckMetricRateMsResponse ( self, payloadInstance ):
+
+        # BUG: We should verify request_id for defense-in-depth / belt-and-suspenders.  How?
+
+        responseName = 'CheckMetricRateResponse'
+
+        value, scale, result = map(int, payloadInstance.readFields( ( 'value', 'scale', 'result' ) ) )
+
+        value *= 10**scale
+        value *= 1000 # rate per ms -> rate per second
+
+        payload = dict( value=value, result=CHECK_RATE_MS_RESPONSE_STRINGS[result] )
+
+        return ( responseName, payload )
+
+
+    def _serializeCheckMetricSampleRateMsResponse ( self, payloadInstance ):
+
+        # BUG: We should verify request_id for defense-in-depth / belt-and-suspenders.  How?
+
+        responseName = 'CheckMetricSampleRateResponse'
+
+        value, scale, result = map(int, payloadInstance.readFields( ( 'value', 'scale', 'result' ) ) )
+
+        value *= 10**scale
+        value *= 1000 # rate per ms -> rate per second
+
+        payload = dict( value=value, result=CHECK_SAMPLE_RATE_MS_RESPONSE_STRINGS[result] )
+
+        return ( responseName, payload )
+
+
+    def _serializeCheckMetricError ( self, payloadInstance ):
+
+        # BUG: We should verify request_id for defense-in-depth / belt-and-suspenders.  How?
+
+        responseName = 'CheckMetricError'
+
+        errorCode   = payloadInstance.readField( 'type' )
+        errorString = CHECK_VALUE_ERROR_STRINGS[ errorCode ]
+
+        payload = dict( error = errorString )
+
+        return ( responseName, payload )
+
+
     # Helper code:
     @staticmethod
     def _getRequestFields ( data, keys ):

src/python/saturnalia/web/tasks/checkmetrictask.py

+#!/usr/bin/python
+from __future__ import with_statement 
+
+# task imports
+from task.basetask       import Task
+
+from saturnalia.messagetypes import Query                           , \
+                                    QueryResponse                   , \
+                                    CheckMetricMeanValue            , \
+                                    CheckMetricMeanValueResponse    , \
+                                    CheckMetricRateMs               , \
+                                    CheckMetricRateMsResponse       , \
+                                    CheckMetricSampleRateMs         , \
+                                    CheckMetricSampleRateMsResponse , \
+                                    CheckMetricError                , \
+                                    EventSinkRouteError             , \
+                                    UndeliverableMessage
+
+from saturnalia.messagebuilder   import UnknownMessageTag
+from saturnalia.messagecontainer import ContainerCodec
+from saturnalia.util             import getSlotStartTime
+
+# our imports
+from saturnalia.tile.container            import TileContainerCodec
+from saturnalia.tile.metric               import MetricAggregator
+
+from saturnalia.slot   import SUM_INDEX, COUNT_INDEX, COUNT_UNITS_PER_EVENT
+
+from saturnalia.config import ConfigReader
+
+from math import log10
+
+QUERY_MESSAGE_TAG                        = Query                           ().getMessageTag()
+CHECK_METRIC_MEAN_VALUE_TAG              = CheckMetricMeanValue            ().getMessageTag()
+CHECK_METRIC_RATE_MS_TAG                 = CheckMetricRateMs               ().getMessageTag()
+CHECK_METRIC_SAMPLE_RATE_MS_TAG          = CheckMetricSampleRateMs         ().getMessageTag()
+CHECK_METRIC_MEAN_VALUE_RESPONSE_TAG     = CheckMetricMeanValueResponse    ().getMessageTag()
+CHECK_METRIC_RATE_MS_RESPONSE_TAG        = CheckMetricRateMsResponse       ().getMessageTag()
+CHECK_METRIC_SAMPLE_RATE_MS_RESPONSE_TAG = CheckMetricSampleRateMsResponse ().getMessageTag()
+CHECK_METRIC_ERROR_TAG                   = CheckMetricError                ().getMessageTag()
+
+COUNT_SCALE = int( log10( 1 / float( COUNT_UNITS_PER_EVENT ) ) )
+
+COUNT_EXTRA_SCALE = -3
+
+
+class CheckMetricTask (Task):
+
+    @staticmethod
+    def defaultFactory ( config, logger, routerHandle ):
+        return CheckMetricTask( config, logger, routerHandle )
+
+
+    def __init__ ( self, config, logger, router ):
+
+
+        self._config     = config
+        self._logger     = logger
+
+        self._queries        = {} # BUG: leaks memory on queries which aren't replied to
+
+        eventHandlers = {
+            CheckMetricMeanValue    : self._handleCheckMetricMeanValue    ,
+            CheckMetricRateMs       : self._handleCheckMetricRateMs       ,
+            CheckMetricSampleRateMs : self._handleCheckMetricSampleRateMs ,
+            QueryResponse           : self._handleQueryResponse           ,
+            UndeliverableMessage    : self._handleUndeliverableMessage    ,
+            EventSinkRouteError     : self._handleEventSinkRouteError     ,
+        }
+
+        Task.__init__( self, eventHandlers, router ) 
+
+    def _sendQuery ( self, handleUsed, originalQuery, deadline, tenant, name, startTime, endTime ):
+
+        messageId = self.getNextMessageId()
+
+        resolution = (endTime - startTime) / 300
+
+        data = {
+            'tenant'        :   tenant       ,
+            'name'          :   name         ,
+            'type'          :   Query.METRIC ,
+            'resolution'    :   resolution   ,
+            'start_time'    :   startTime    ,
+            'end_time'      :   endTime      ,
+            }
+
+        try:
+            newMessage = self._messageBuilder.makeMessage( data, handleUsed, messageId, deadline, QUERY_MESSAGE_TAG )
+
+        except UnknownMessageTag, e:
+            self._logger.error( str( e ) )
+
+        else:
+            self._queries[messageId] = originalQuery.copyToHeap(), startTime, endTime
+
+            self.getRouterHandle().route( newMessage )
+
+
+    def _handleCheckMetricMeanValue ( self, message, handleUsed ):
+
+        #BUG: this should validate messages and send appropriate errors
+
+        deadline  = message.getDeadline()
+        tenant    = message.readField( 'tenant'     )
+        name      = message.readField( 'name'       )
+        startTime = message.readField( 'start_time' )
+        endTime   = message.readField( 'end_time'   )
+        
+        self._sendQuery( handleUsed, message, deadline, tenant, name, startTime, endTime )
+
+
+    def _handleCheckMetricRateMs ( self, message, handleUsed ):
+
+        #BUG: this should validate messages and send appropriate errors
+
+        deadline  = message.getDeadline()
+        tenant    = message.readField( 'tenant'     )
+        name      = message.readField( 'name'       )
+        startTime = message.readField( 'start_time' )
+        endTime   = message.readField( 'end_time'   )
+        
+        self._sendQuery( handleUsed, message, deadline, tenant, name, startTime, endTime )
+
+
+    def _handleCheckMetricSampleRateMs ( self, message, handleUsed ):
+
+        #BUG: this should validate messages and send appropriate errors
+
+        deadline  = message.getDeadline()
+        tenant    = message.readField( 'tenant'     )
+        name      = message.readField( 'name'       )
+        startTime = message.readField( 'start_time' )
+        endTime   = message.readField( 'end_time'   )
+        
+        self._sendQuery( handleUsed, message, deadline, tenant, name, startTime, endTime )
+
+
+    def _getQuery ( self, requestId ):
+
+        # BUG: need to clean up expired queries somewhere
+        return self._queries.pop( requestId )
+
+
+    def _handleQueryResponse ( self, message, handleUsed ):
+
+        requestId   = message.readField( "request_id" )
+        tiles       = message.readField( "data"       )
+
+        originalQuery, startTime, endTime = self._getQuery( requestId )
+
+        scale      = None
+        resolution = None
+
+        count     = 0
+        sum       = 0
+        slotCount = 0
+
+        metricConfig = ConfigReader({}) # BUG: Get from config.
+
+        containerCodec = ContainerCodec ( self._messageBuilder )
+        tileCodec      = TileContainerCodec ( containerCodec )
+
+        for tileString in tiles:
+
+            tile = tileCodec.deserializeAggregatorFromBytes( metricConfig, self._logger, tileString )
+
+            if not isinstance( tile, MetricAggregator ):
+                self._logger.error( 'Got non-metric tile: {0!r} (metricd bug?)'.format( type( tile ) ) )
+                continue
+
+            if scale == None:
+                scale = tile.getScale()
+
+            elif tile.getScale() != scale:
+                self._logger.error( "Got tiles with different scales, this shouldn't happen." )
+                continue
+
+            if resolution == None:
+                resolution = tile.getResolution()
+
+            elif tile.getResolution() != resolution:
+                self._logger.error( "Got tiles with different resolutions, this shouldn't happen." )
+                continue
+
+            for ( slotTime, slot ) in tile.getActiveSlotIterator():
+
+                if slotTime <= startTime:
+                    continue
+
+                if slotTime > endTime:
+                    break
+
+                count     += slot[ COUNT_INDEX ]
+                sum       += slot[ SUM_INDEX   ]
+                slotCount += 1
+
+        if count == 0 or slotCount == 0:
+            self._replyWithError( handleUsed, originalQuery, CheckMetricError.NO_DATA )
+            return
+
+        queryType = originalQuery.getMessageType()
+
+        if queryType == CHECK_METRIC_MEAN_VALUE_TAG:
+            self._sendCheckMetricMeanValueResponse( handleUsed, originalQuery, count, sum, slotCount, scale )
+
+        elif queryType == CHECK_METRIC_RATE_MS_TAG:
+            self._sendCheckMetricRateMsResponse( handleUsed, originalQuery, count, sum, slotCount, scale, resolution )
+
+        elif queryType == CHECK_METRIC_SAMPLE_RATE_MS_TAG:
+
+            rangeMs = getSlotStartTime( endTime, resolution ) - getSlotStartTime( startTime, resolution )
+
+            self._sendCheckMetricSampleRateMsResponse( handleUsed, originalQuery, count, sum, rangeMs )
+
+        else:
+            self._replyWithError( handleUsed, originalQuery, CheckMetricError.INTERNAL_ERROR )
+            return # this won't be possible if we validate messages in the handler
+
+
+    def _sendCheckMetricMeanValueResponse ( self, handleUsed, originalQuery, count, sum, slotCount, scale ):
+
+        originalSender    = originalQuery.getFrom()
+        originalMessageId = originalQuery.getMessageId()
+        deadline          = originalQuery.getDeadline()
+
+        minValue  = originalQuery.readField( 'min' )
+        maxValue  = originalQuery.readField( 'max' )
+
+        value     = sum / (count / COUNT_UNITS_PER_EVENT)
+        minValue /= 10**scale
+        maxValue /= 10**scale
+
+        result = CheckMetricMeanValueResponse.PASS
+
+        if value > maxValue:
+            result = CheckMetricMeanValueResponse.FAIL_MAX
+
+        elif value < minValue:
+            result = CheckMetricMeanValueResponse.FAIL_MIN
+
+        data = {
+            'request_id'    :       originalMessageId   ,
+            'value'         :       value               ,
+            'scale'         :       scale               ,
+            'result'        :       result              ,
+            }
+
+        self.buildAndSendMessage( handleUsed, data, CHECK_METRIC_MEAN_VALUE_RESPONSE_TAG, deadline, originalSender )
+
+
+    def _sendCheckMetricRateMsResponse ( self, handleUsed, originalQuery, count, sum, slotCount, scale, resolution ):
+
+        originalSender    = originalQuery.getFrom()
+        originalMessageId = originalQuery.getMessageId()
+        deadline          = originalQuery.getDeadline()
+
+        minValue  = originalQuery.readField( 'min' )
+        maxValue  = originalQuery.readField( 'max' )
+
+        value = sum / ( slotCount * resolution )
+
+        minValue /= 10**scale
+        maxValue /= 10**scale
+
+        result = CheckMetricRateMsResponse.PASS
+
+        if value > maxValue:
+            result = CheckMetricRateMsResponse.FAIL_MAX
+
+        elif value < minValue:
+            result = CheckMetricRateMsResponse.FAIL_MIN
+
+        data = {
+            'request_id'    :       originalMessageId   ,
+            'value'         :       value               ,
+            'scale'         :       scale               ,
+            'result'        :       result              ,
+            }
+
+        self.buildAndSendMessage( handleUsed, data, CHECK_METRIC_RATE_MS_RESPONSE_TAG, deadline, originalSender )
+
+
+    def _sendCheckMetricSampleRateMsResponse ( self, handleUsed, originalQuery, count, sum, rangeMs ):
+
+        originalSender    = originalQuery.getFrom()
+        originalMessageId = originalQuery.getMessageId()
+        deadline          = originalQuery.getDeadline()
+
+        minValue  = originalQuery.readField( 'min' )
+        maxValue  = originalQuery.readField( 'max' )
+
+        value = float(count) / rangeMs
+
+        scale = COUNT_SCALE
+
+        minValue /= 10**scale
+        maxValue /= 10**scale
+
+        result = CheckMetricSampleRateMsResponse.PASS
+
+        if value > maxValue:
+            result = CheckMetricSampleRateMsResponse.FAIL_MAX
+
+        elif value < minValue:
+            result = CheckMetricSampleRateMsResponse.FAIL_MIN
+
+        value /= 10**COUNT_EXTRA_SCALE # this is necessary so that we can send sample rates under 1/second
+        scale += COUNT_EXTRA_SCALE
+
+        data = {
+            'request_id'    :       originalMessageId   ,
+            'value'         :       value               ,
+            'scale'         :       scale               ,
+            'result'        :       result              ,
+            }
+
+        self.buildAndSendMessage( handleUsed, data, CHECK_METRIC_SAMPLE_RATE_MS_RESPONSE_TAG, deadline, originalSender )
+
+
+    def _handleUndeliverableMessage ( self, message, handleUsed ):
+        logMessage = "got undeliverable message!\n%s" % ( message, )
+        self._logger.error( logMessage )
+
+    def _handleEventSinkRouteError ( self,  message, handleUsed ):
+
+        requestId           = message.readField( "messageId" )
+        originalQuery, _, _ = self._getQuery( requestId )
+
+        self._replyWithError( handleUsed, originalQuery, CheckMetricError.UNKNOWN_NAME )
+
+
+    def _replyWithError( self, handleUsed, originalQuery, errorCode ):
+
+        deadline        = originalQuery.getDeadline()
+        requestId       = originalQuery.getMessageId()
+        originalSender  = originalQuery.getFrom()
+
+        data = {
+            'request_id' : requestId ,
+            'type'       : errorCode ,
+            }
+
+        self.buildAndSendMessage( handleUsed, data, CHECK_METRIC_ERROR_TAG, deadline, originalSender )
+
+

src/python/saturnalia/web/tasks/datarequesttask.py

 from task.datainstance   import DataInstance
 from task.hashutil       import convertUInt64ToHex
 
-from saturnalia.messagetypes import Query                , \
-                                    QueryResponse        , \
-                                    SendQuery            , \
-                                    WebQueryResponse     , \
-                                    EventSinkRouteError  , \
+from saturnalia.messagetypes import Query                      , \
+                                    QueryResponse              , \
+                                    SendQuery                  , \
+                                    WebQueryResponse           , \
+                                    EventSinkRouteError        , \
                                     UndeliverableMessage
 
 from saturnalia.messagebuilder   import UnknownMessageTag

src/python/saturnalia/web/tasks/proxytask.py

                                     AccountProvisionerVerifyEmail             , \
                                     AccountManagerGetBalance                  , \
                                     AccountManagerGetCredentials              , \
-                                    SendQuery
+                                    SendQuery                                 , \
+                                    CheckMetricMeanValue                      , \
+                                    CheckMetricRateMs                         , \
+                                    CheckMetricSampleRateMs
+
 
 ALLOWED_MESSAGE_TYPES = [
     ComponentDbAdd                            ,
     AccountManagerGetBalance                  ,
     AccountManagerGetCredentials              ,
     SendQuery                                 ,
+    CheckMetricMeanValue                      ,
+    CheckMetricRateMs                         ,
+    CheckMetricSampleRateMs                   ,
     ]
 
 ALWAYS_PUBLIC_MESSAGE_TYPES = [

src/python/statmoverclient/data_tool.py

 from statmoverclient.cli        import SaturnaliaOptionParser
 from statmoverclient.restclient import SaturnaliaRESTClient
 
+NAGIOS_CRITICAL_STATUS = 2
 
 USAGE = """\
 %prog COMMAND [...]
 
 """
 
-
-
 def main ( args = sys.argv[1:] ):
     ( config, cmdFunc, cmdArgs ) = parseArgs( args )
 
 
 
 def parseArgs ( args ):
+
+    if len(args) > 0 and args[0] in HiddenCommands:
+        Commands.update( HiddenCommands )
+
     usage = USAGE + 'Commands:\n'
     usage += '\n'.join( [ cmdFunc.Usage for (_, cmdFunc) in sorted( Commands.items() ) ] )
 
 
     try:
         cmdFunc = Commands[ cmdName ]
+
     except KeyError:
         knownCommands = ' '.join( sorted( Commands.keys() ) )
         parser.error( 'Unknown command: %r\nKnown commands: %s' % ( cmdName, knownCommands ) )
 
   Query for data over the rest interface and print JSON results on stdout.
 
+  CAVEAT EMPTOR! This command is "hidden" (not listed in data-tool's usage if
+  you don't know its name) because it has undocumented undesirable edge cases.
+
   Arguments:
 
   TYPE       - Either "annotation" or "metric".
 """
 
 
+def checkmetric ( restUrl, checkType, name, intervalHours, min, max ):
+    """The checkmetric command."""
+
+    intervalMs = int( intervalHours ) * 60 * 60 * 1000
+
+    min = float( min )
+    max = float( max)
+
+    client = SaturnaliaRESTClient( restUrl )
+
+    result = client.checkmetric( checkType, name, intervalMs, min, max )
+
+    json.dump( result, sys.stdout )
+    sys.stdout.write('\n')
+
+    if result["result"] == "OK":
+        return 0
+
+    else:
+        return NAGIOS_CRITICAL_STATUS
+
+
+checkmetric.ArgCount = 5
+checkmetric.Usage = """\
+%prog checkmetric CHECK_TYPE NAME INTERVAL MIN MAX
+
+  Check if the recent value of a metric is within a given range.
+
+  Arguments:
+
+  CHECK_TYPE - Either "MeanValue", "Rate", or "SampleRate"
+  NAME       - The event sink name.
+  INTERVAL   - The amount of time, in hours, to check over.
+  MIN        - The minimum acceptable value.
+  MAX        - The maximum acceptable value.
+
+  MIN and MAX are floating point numbers. "inf" and "-inf" are
+  valid inputs.
+
+  The result will be printed as a one-line json document. If the value is not
+  within the target range, the exit status will be 2, making this command
+  suitable for use in a nagios or icinga command definition.
+"""
+
 Commands = {
+    checkmetric.__name__: checkmetric,
+    }
+
+HiddenCommands = {
     query.__name__: query,
     }
 
 
 
 if __name__ == '__main__':
-    main()
+    sys.exit( main() )

src/python/statmoverclient/restclient.py

 
 
 USAGE = """\
+NOTE: using statmoverclient.restclient directly is deprecated. Please use
+component-tool, eventsink-tool, and data-tool instead.
+
 %prog [options] componentsJsonPath [ failedComponentsPath ]
 
 Provision the components stored in componentsJsonPath.  Store all failed
 
         raise ResponseError( 'Unexpected response: %r %r' % ( responseType, responsePayload ) )
 
+
     def query ( self, type, name, resolution, startTime, endTime ):
 
         requestType = 'Query'
         return self.makeCheckedRequest( requestType, requestPayload, responseType )
 
 
+    def checkmetric ( self, checkType, name, intervalMs, min, max ):
+
+        if checkType == 'MeanValue':
+            requestType  = 'CheckMetricMeanValue'
+            responseType = 'CheckMetricMeanValueResponse'
+        
+        elif checkType == 'Rate':
+            requestType  = 'CheckMetricRate'
+            responseType = 'CheckMetricRateResponse'
+
+        elif checkType == 'SampleRate':
+            requestType  = 'CheckMetricSampleRate'
+            responseType = 'CheckMetricSampleRateResponse'
+
+        else:
+            raise SaturnaliaRESTError( "Unknown CheckMetric type %r" % (checkType,) )
+
+        requestPayload = dict(
+            name     = name         ,
+            interval = intervalMs   ,
+            min      = min          ,
+            max      = max          ,
+            )
+
+        return self.makeCheckedRequest( requestType, requestPayload, responseType )
+
+
 def _basicAuthFromUrl ( url ):
     """
     Move username and password from a URL into an Authentication header.

test-bin/prepare-sweb-config.sh

 
     configFile="$dest/service/sweb/config/sweb.conf"
     mailTemplatePath="$dest/src/email-templates/verification-email.template"
-    baseComponentsPath="$dest/service/basecomponents.json"
+    baseComponentsPath="$dest/basecomponents.json"
 
     documentRoot=$dest/src/html
     thisHost=$(hostname)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.