numerousapp 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/numerousapp.rb +83 -27
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5d1de99d002e38617461074b068f12e9ea25951
4
- data.tar.gz: faefd87f5da4d43832e227fd2552cd8611f0daf9
3
+ metadata.gz: 238c5ee878f1b0d248d60726db930695f6179c62
4
+ data.tar.gz: edde3c80e6ee8396841f11180b18162e44d14f97
5
5
  SHA512:
6
- metadata.gz: c13d42b013d15a26e0590ec9f032ebae570c978ccbf7babd0a28179349045c00accba926f851e7020da745741c92f13a123d2e030c2ffa692fb1d0fb0641c8a4
7
- data.tar.gz: 6680ae04cfe5dfe28577e529d2071d7989d45937cc6578c28bb5de8903d3cff5da26f008577803bd87cde792b9aef34c273ba0ba8211d72ecca3cdbe4fdb20eb
6
+ metadata.gz: f4eec611d9bfce7c676552229234935f096cb1542a9bee7b233fc73108f02ef571f3596941b833b11d0451cab3ef7d3e862bc3b57b0f943569e333d1b503b55c
7
+ data.tar.gz: b631ed7da965788038cee88bbbda20c5b9811e34cc86111b1e759f068e2335b9d5607ff7882774bbc72e230416f1ccd982b70ad6d21e33827d31551206dacebf
data/lib/numerousapp.rb CHANGED
@@ -153,12 +153,13 @@ class NumerousClientInternals
153
153
  #
154
154
  # and the default policy uses the "data" as a hash of parameters:
155
155
  # :voluntary -- the threshold point for voluntary backoff
156
+ # :volmaxdelay -- arbitrary maximum *voluntary delay* time
156
157
  #
157
158
  @arbitraryMaximumTries = 10
158
- voluntary = { voluntary: 40}
159
+ voluntary = { voluntary: 40, volmaxdelay: 5}
159
160
  # you can keep the dflt throttle but just alter the voluntary param, this way:
160
161
  if throttleData and not throttle
161
- voluntary = throttleData
162
+ voluntary = voluntary.merge(throttleData)
162
163
  end
163
164
  @throttlePolicy = [ThrottleDefault, voluntary, nil]
164
165
  if throttle
@@ -222,7 +223,7 @@ class NumerousClientInternals
222
223
 
223
224
  protected
224
225
 
225
- VersionString = '20150618-1.2.2'
226
+ VersionString = '20150713-1.2.3'
226
227
 
227
228
  MethMap = {
228
229
  GET: Net::HTTP::Get,
@@ -596,16 +597,52 @@ class NumerousClientInternals
596
597
  return nil # the subclasses return (should return) their own self
597
598
  end
598
599
 
600
+
599
601
  #
600
602
  # The default throttle policy.
601
603
  # Invoked after the response has been received and we are supposed to
602
604
  # return true to force a retry or false to accept this response as-is.
603
605
  #
604
606
  # The policy this implements:
605
- # if we are "getting close" to our limit, arbitrarily delay ourselves.
607
+ # * if "getting close" to the limit, arbitrarily delay ourselves.
608
+ # See ComputeVoluntaryDelay above for details on that policy
609
+ #
610
+ # * if we truly got spanked with "Too Many Requests"
611
+ # then delay the amount of time the server told us to delay.
612
+ #
613
+ # The Voluntary delay policy works like this:
614
+ #
615
+ # Given N API calls remaining and T seconds until fresh allotment,
616
+ # compute a N-per-T rate delay so the hard rate limit probably won't
617
+ # hit (there is no guarantee bcs multiple clients can be running).
618
+ #
619
+ # Example: 20 APIs remaining and 5 seconds until fresh allocation.
620
+ # A delay of 250msec per API ensures we (approximately) don't hit the
621
+ # limit. Always remember the point here is just to TRY to be NICE.
622
+ # It's not important to be fussy about exactness.
623
+ #
624
+ # In effect the concept is to "smear" an inevitable rate-limit delay
625
+ # over the tail end of the API rate allocation rather than hitting
626
+ # the hard limit and encountering a long (e.g., 30 second) hard delay.
627
+ #
628
+ # When there are only a few APIs left and a lot of time, this could
629
+ # impose long delays. E.g., rateleft 2, but 40 seconds to go until
630
+ # fresh. Although this "shouldn't" happen if you have a single thread
631
+ # using this smear algorithm, it can certainly happen with multiple
632
+ # threads or multiple processes all individually consuming APIs.
633
+ # In this scenario you're going to inevitably hit the hard cap anyway.
634
+ # Therefore: voluntary delay is arbitrarily capped to a parameter provided
635
+ # in the throttledata (set up during initialization)
636
+ #
637
+ # This has been stress-tested "in the wild" by running code doing
638
+ # a metric.read() in a loop; theoretically such code should run at
639
+ # 300 API calls per minute -- and it does, either with this voluntary
640
+ # throttling or without it. If you are trying to run faster than 300
641
+ # per minute, it's all just a question of *how* you want to experience
642
+ # your (ultimately server-imposed) API throttling, not *if* (or how much).
643
+ #
644
+ # Speed Limit: 300 API/minute. It's The Law. :)
606
645
  #
607
- # if we truly got spanked with "Too Many Requests"
608
- # then delay the amount of time the server told us to delay.
609
646
  #
610
647
  # The arguments supplied to us are:
611
648
  # nr is the Numerous
@@ -644,8 +681,8 @@ class NumerousClientInternals
644
681
  # All of this seems overly general for what amounts to "sleep sometimes"
645
682
  #
646
683
 
684
+
647
685
  ThrottleDefault = Proc.new do |nr, tparams, td, up|
648
- rateleft = tparams[:rateRemaining]
649
686
  attempt = tparams[:attempt] # note: is zero on very first try
650
687
  stats = tparams[:statistics]
651
688
  stats[:throttleDefaultCalls] += 1
@@ -657,30 +694,35 @@ class NumerousClientInternals
657
694
  end
658
695
  end
659
696
 
660
- backarray = [ 2, 5, 15, 30, 60 ]
661
- if attempt < backarray.length
662
- backoff = backarray[attempt]
663
- else
664
- stats[:throttleMaxed] += 1
665
- next false # too many tries
666
- end
667
-
668
697
  # if we weren't told to back off, no need to retry
669
698
  if tparams[:resultCode] != 429
699
+ #
670
700
  # but if we are closing in on the limit then slow ourselves down
671
701
  # note that some errors don't communicate rateleft so we have to
672
702
  # check for that as well (will be -1 here if wasn't sent to us)
673
703
  #
674
- # at constructor time our "throttle data" (td) was set up with the
675
- # voluntary arbitrary limit
676
- if rateleft >= 0 and rateleft < td[:voluntary]
704
+ # the point of this policy is to protect OTHER implementations
705
+ # that might not be aware of rate-limiting (i.e., we're being
706
+ # generous here by slowing ourselves down to try to avoid
707
+ # reaching the actual rate limit)
708
+ #
709
+ # at constructor time our "throttle data" (td) was set up with
710
+ # the 'voluntary' arbitrary limit
711
+ nAPIs_left = tparams[:rateRemaining]
712
+ if nAPIs_left >= 0 and nAPIs_left < td[:voluntary]
677
713
  stats[:throttleVoluntaryBackoff] += 1
678
- # arbitrary .. 1sec if more than half left, 3 secs if less
679
- if (rateleft*2) > td[:voluntary]
680
- sleep(1)
714
+ t = tparams[:rateReset] # time until fresh allocation
715
+ if t > 0 and nAPIs_left > 0
716
+ # force floating point
717
+ secs_per_API = (t + 0.001) / nAPIs_left
681
718
  else
682
- sleep(3)
719
+ secs_per_API = 0.5 # arbitrary when no info
683
720
  end
721
+ # arbitrary voluntary delay cap
722
+ dt = [td[:volmaxdelay], secs_per_API].min
723
+
724
+ stats[:throttleVoluntaryDelays] += dt
725
+ sleep(dt)
684
726
  end
685
727
  next false # no retry
686
728
  end
@@ -688,6 +730,14 @@ class NumerousClientInternals
688
730
  # decide how long to delay ... we just wait for as long as the
689
731
  # server told us to (plus "backoff" seconds slop to really be sure we
690
732
  # aren't back too soon)
733
+ backarray = [ 0.75, 1.5, 5, 15, 45 ]
734
+ if attempt < backarray.length
735
+ backoff = backarray[attempt]
736
+ else
737
+ stats[:throttleMaxed] += 1
738
+ next false # too many tries
739
+ end
740
+
691
741
  stats[:throttle429] += 1
692
742
  sleep(tparams[:rateReset] + backoff)
693
743
  next true
@@ -1125,9 +1175,8 @@ class NumerousMetric < NumerousClientInternals
1125
1175
  #
1126
1176
  # It can also be a metric's web link, e.g.:
1127
1177
  # http://n.numerousapp.com/m/1x8ba7fjg72d
1128
- #
1129
- # in which case we "just know" that the tail is a base36
1130
- # encoding of the ID.
1178
+ # or the "embed" (/e/ vs /m/) variant, in which case we "just know"
1179
+ # that the tail is a base36 encoding of the ID.
1131
1180
  #
1132
1181
  # The decoding logic here makes the specific assumption that
1133
1182
  # the presence of a '/' indicates a non-naked metric ID. This
@@ -1155,7 +1204,7 @@ class NumerousMetric < NumerousClientInternals
1155
1204
  fields = id.split('/')
1156
1205
  if fields.length() == 1
1157
1206
  actualId = fields[0]
1158
- elsif fields[-2] == "m"
1207
+ elsif [ "m", "e" ].include? fields[-2]
1159
1208
  actualId = fields[-1].to_i(36)
1160
1209
  else
1161
1210
  actualId = fields[-1]
@@ -1625,7 +1674,14 @@ class NumerousMetric < NumerousClientInternals
1625
1674
  j['action'] = 'ADD'
1626
1675
  end
1627
1676
  if updated
1628
- j['updated'] = updated
1677
+ # if you gave us a formattable time try converting it
1678
+ begin
1679
+ ts = updated.strftime('%Y-%m-%dT%H:%M:%S.')
1680
+ ts += ("%03dZ" % ((updated.usec+500)/1000))
1681
+ rescue NoMethodError # just take your argument
1682
+ ts = updated # which should be a string already
1683
+ end
1684
+ j['updated'] = ts
1629
1685
  end
1630
1686
 
1631
1687
  @cachedHash = nil # will need to refresh cache on next access
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: numerousapp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Webber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-18 00:00:00.000000000 Z
11
+ date: 2015-07-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Classes implementing the NumerousApp REST APIs for metrics. Requires
14
14
  Ruby 2.x