using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Timers; using BirdsiteLive.Twitter.Models; namespace BirdsiteLive.Statistics.Domain { public interface ITwitterStatisticsHandler { void CalledUserApi(); void CalledTweetApi(); void CalledTimelineApi(); ApiStatistics GetStatistics(); int GetCurrentUserCalls(); } //Rate limits: https://developer.twitter.com/en/docs/twitter-api/v1/rate-limits public class TwitterStatisticsHandler : ITwitterStatisticsHandler { private static int _userCalls; private static int _tweetCalls; private static int _timelineCalls; private static ConcurrentDictionary _snapshots = new ConcurrentDictionary(); private static System.Timers.Timer _resetTimer; #region Ctor public TwitterStatisticsHandler() { if (_resetTimer == null) { _resetTimer = new System.Timers.Timer(); _resetTimer.Elapsed += OnTimeResetEvent; _resetTimer.Interval = 15 * 60 * 1000; // 15" _resetTimer.Enabled = true; } } #endregion private void OnTimeResetEvent(object sender, ElapsedEventArgs e) { // Add snapshot var snapshot = new ApiStatisticsSnapshot(_userCalls, _tweetCalls, _timelineCalls); bool success; do { success = _snapshots.TryAdd(snapshot.SnapshotDate, snapshot); } while (!success); // Reset Interlocked.Exchange(ref _userCalls, 0); Interlocked.Exchange(ref _tweetCalls, 0); Interlocked.Exchange(ref _timelineCalls, 0); // Clean up var now = DateTime.UtcNow; var oldSnapshots = _snapshots.Keys.Where(x => (now - x).TotalHours > 24).ToList(); foreach (var old in oldSnapshots) _snapshots.TryRemove(old, out var data); } public int GetCurrentUserCalls() { return _userCalls; } public void CalledUserApi() //GET users/show - 300/15mins { Interlocked.Increment(ref _userCalls); } public void CalledTweetApi() //GET statuses/lookup - 300/15mins { Interlocked.Increment(ref _tweetCalls); } public void CalledTimelineApi() // GET statuses/user_timeline - 1500/15 mins { Interlocked.Increment(ref _timelineCalls); } public ApiStatistics GetStatistics() { var snapshots = _snapshots.Values.ToList(); var userCalls = snapshots.Select(x => x.UserCalls).ToList(); var tweetCalls = snapshots.Select(x => x.TweetCalls).ToList(); var timelineCalls = snapshots.Select(x => x.TimelineCalls).ToList(); return new ApiStatistics { UserCallsCountMin = userCalls.Any() ? userCalls.Min() : 0, UserCallsCountAvg = userCalls.Any() ? (int)userCalls.Average() : 0, UserCallsCountMax = userCalls.Any() ? userCalls.Max() : 0, UserCallsMax = 300, TweetCallsCountMin = tweetCalls.Any() ? tweetCalls.Min() : 0, TweetCallsCountAvg = tweetCalls.Any() ? (int)tweetCalls.Average() : 0, TweetCallsCountMax = tweetCalls.Any() ? tweetCalls.Max() : 0, TweetCallsMax = 300, TimelineCallsCountMin = timelineCalls.Any() ? timelineCalls.Min() : 0, TimelineCallsCountAvg = timelineCalls.Any() ? (int)timelineCalls.Average() : 0, TimelineCallsCountMax = timelineCalls.Any() ? timelineCalls.Max() : 0, TimelineCallsMax = 1000 }; } } internal class ApiStatisticsSnapshot { #region Ctor public ApiStatisticsSnapshot(int userCalls, int tweetCalls, int timelineCalls) { UserCalls = userCalls; TweetCalls = tweetCalls; TimelineCalls = timelineCalls; SnapshotDate = DateTime.UtcNow; } #endregion public DateTime SnapshotDate { get; } public int UserCalls { get; set; } public int TweetCalls { get; set; } public int TimelineCalls { get; set; } } }