예제 #1
0
        /// <summary>
        /// Wrapper for Github calls for each component of the overall health.
        /// </summary>
        /// <returns></returns>
        public override async Task <HealthMetrics> GetHealth()
        {
            Logger.Debug("GetHealth({0}/{1})", purl.Namespace, purl.Name);

            var health           = new HealthMetrics();
            var initialRateLimit = await Client.Miscellaneous.GetRateLimits();

            var remaining = initialRateLimit.Resources.Core.Remaining;

            if (remaining < 500)
            {
                Logger.Warn("Your GitHub access token has only {0}/5000 tokens left.", remaining);
            }
            if (remaining == 0)
            {
                throw new Exception("No remaining API tokens.");
            }

            await GetProjectSizeHealth(health);
            await GetIssueHealth(health);
            await GetCommitHealth(health);
            await GetContributorHealth(health);
            await GetRecentActivityHealth(health);
            await GetReleaseHealth(health);
            await GetPullRequestHealth(health);


            return(health);
        }
예제 #2
0
        /// <summary>
        /// Calculates pull request health
        /// </summary>
        /// <param name="metrics"></param>
        /// <returns></returns>
        public async Task GetPullRequestHealth(HealthMetrics metrics)
        {
            Logger.Debug($"GetPullRequestHealth('{purl.Namespace}', '{purl.Name}')");
            double pullRequestHealth = 0;

            int pullRequestOpen   = 0;
            int pullRequestClosed = 0;

            var pullRequestRequest = new PullRequestRequest()
            {
                State = ItemStateFilter.All
            };

            var pullRequests = await Client.PullRequest.GetAllForRepository(purl.Namespace, purl.Name, pullRequestRequest, DEFAULT_API_OPTIONS);

            // Gather raw data
            foreach (var pullRequest in pullRequests)
            {
                if (pullRequest.State == ItemState.Open)
                {
                    pullRequestOpen++;
                }
                else if (pullRequest.State == ItemState.Closed)
                {
                    pullRequestClosed++;
                }
            }
            if (pullRequestOpen + pullRequestClosed > 0)
            {
                pullRequestHealth = 100.0 * pullRequestClosed / (pullRequestOpen + pullRequestClosed);
            }
            metrics.PullRequestHealth = pullRequestHealth;
        }
예제 #3
0
        /// <summary>
        /// Calculates commit health.
        /// </summary>
        /// <returns></returns>
        public async Task GetCommitHealth(HealthMetrics metrics)
        {
            Logger.Debug($"GetCommitHealth('{purl.Namespace}', '{purl.Name}')");

            double weightedCommits    = 0.0;
            int    totalCommits       = 0;
            int    totalRecentCommits = 0;

            var contributors = await Client.Repository.Statistics.GetContributors(purl.Namespace, purl.Name);

            totalCommits = contributors.Sum(c => c.Total);

            var recentCommitActivity = await Client.Repository.Statistics.GetCommitActivity(purl.Namespace, purl.Name);

            foreach (var weekActivity in recentCommitActivity.Activity)
            {
                totalRecentCommits += weekActivity.Total;
                var numDaysAgo = (DateTime.Now - weekActivity.WeekTimestamp).Days;
                weightedCommits += (6.0 * weekActivity.Total / Math.Log(numDaysAgo + 14));
            }

            double commitHealth = 7.0 * Math.Log(totalCommits) + weightedCommits;

            metrics.CommitHealth = Math.Round(Clamp(commitHealth), 1);
        }
예제 #4
0
        /**
         * Determine the most recent activity (any type).
         *
         * The API only returns events from the last 90 days
         *
         * Most recent activity.
         * Health calculated as the mean number of seconds in the past 30 activities.
         */
        public async Task GetRecentActivityHealth(HealthMetrics metrics)
        {
            Logger.Trace("GetRecentActivityHealth({0}, {1})", purl.Namespace, purl.Name);

            var activityList = await Client.Activity.Events.GetAllForRepository(purl.Namespace, purl.Name, DEFAULT_API_OPTIONS);

            double activityHealth = 0.50 * activityList.Count;

            metrics.RecentActivityHealth = Clamp(activityHealth);
        }
예제 #5
0
        /**
         * Release health is defined as:
         * # releases + # tags
         */
        public async Task GetReleaseHealth(HealthMetrics metrics)
        {
            Logger.Trace("GetReleaseHealth({0}, {1})", purl.Namespace, purl.Name);

            var releases = await Client.Repository.Release.GetAll(purl.Namespace, purl.Name, DEFAULT_API_OPTIONS);

            double releaseHealth = releases.Count();

            var tags = await Client.Repository.GetAllTags(purl.Namespace, purl.Name, DEFAULT_API_OPTIONS);

            releaseHealth        += tags.Count();
            metrics.ReleaseHealth = Clamp(releaseHealth);
        }
예제 #6
0
        /**
         * Release health is defined as:
         * # releases + # tags
         */

        public async Task GetReleaseHealth(HealthMetrics metrics)
        {
            Logger.Trace("GetReleaseHealth({0}, {1})", purl.Namespace, purl.Name);

            System.Collections.Generic.IReadOnlyList <Release>?releases = await Client.Repository.Release.GetAll(purl.Namespace, purl.Name, DEFAULT_API_OPTIONS);

            double releaseHealth = releases.Count;

            System.Collections.Generic.IReadOnlyList <RepositoryTag>?tags = await Client.Repository.GetAllTags(purl.Namespace, purl.Name, DEFAULT_API_OPTIONS);

            releaseHealth        += tags.Count;
            metrics.ReleaseHealth = Clamp(releaseHealth);
        }
예제 #7
0
        /// <summary>
        /// Calculate health based on project size.
        /// If the size is less than 100, then the health is the size of the
        /// project. If it's between 100 and 5000, it's 100%. If it's higher
        /// then it starts to approach zero.
        /// </summary>
        /// <returns></returns>
        public async Task GetProjectSizeHealth(HealthMetrics metrics)
        {
            var repo = await Client.Repository.Get(purl.Namespace, purl.Name);

            long health = 100;

            if (repo.Size < 100)
            {
                health = repo.Size;
            }
            else if (repo.Size > 10000)
            {
                health = 500000 / repo.Size;
            }
            metrics.ProjectSizeHealth = Clamp(health);
        }
예제 #8
0
        /**
         * Calculates contributor health for a Repository.
         *
         * This is defined as 6 * (# contributors, subscribers, forks, stargazers), capped at 100
         */
        public async Task GetContributorHealth(HealthMetrics metrics)
        {
            Logger.Trace("GetContributorHealth({0}, {1}", purl.Namespace, purl.Name);

            var repository = await Client.Repository.Get(purl.Namespace, purl.Name);

            double contributorHealth = 6.0 * repository.StargazersCount +
                                       repository.SubscribersCount +
                                       repository.ForksCount;

            if (contributorHealth < 100.0)
            {
                var contributors = await Client.Repository.GetAllContributors(purl.Namespace, purl.Name, DEFAULT_API_OPTIONS);

                contributorHealth += 6.0 * contributors.Count;
            }

            metrics.ContributorHealth = Clamp(contributorHealth);
        }
예제 #9
0
        /**
         * Calculates contributor health for a Repository.
         *
         * This is defined as 6 * (# contributors, subscribers, forks, stargazers), capped at 100
         */

        public async Task GetContributorHealth(HealthMetrics metrics)
        {
            Logger.Trace("GetContributorHealth({0}, {1}", purl.Namespace, purl.Name);

            Repository?repository = await Client.Repository.Get(purl.Namespace, purl.Name);

            double contributorHealth = 6.0 * repository.StargazersCount +
                                       repository.WatchersCount +
                                       repository.ForksCount;

            if (contributorHealth < 100.0)
            {
                System.Collections.Generic.IReadOnlyList <RepositoryContributor>?contributors = await Client.Repository.GetAllContributors(purl.Namespace, purl.Name, DEFAULT_API_OPTIONS);

                contributorHealth += 6.0 * contributors.Count;
            }

            metrics.ContributorHealth = Clamp(contributorHealth);
        }
예제 #10
0
        /// <summary>
        /// Retrieves issue and security issue counts (subset) to minimize calls out.
        /// Note: Octokit Search API was found earlier to be unreliable
        /// </summary>
        /// <returns></returns>
        public async Task GetIssueHealth(HealthMetrics metrics)
        {
            Logger.Trace("GetIssueHealth({0}, {1})", purl.Namespace, purl.Name);

            var securityFlags = new string[] {
                "security", "insecure", "vulnerability", "cve", "valgrind", "xss",
                "sqli ", "vulnerable", "exploit", "fuzz", "injection",
                "buffer overflow", "valgrind", "sql injection", "csrf",
                "xsrf", "pwned", "akamai legacy ssl", "bad cipher ordering",
                "untrusted", "backdoor", "command injection"
            };

            var filter = new RepositoryIssueRequest
            {
                Filter = IssueFilter.All,
                State  = ItemStateFilter.All,
                Since  = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(3 * 365))
            };

            int openIssues           = 0;
            int closedIssues         = 0;
            int openSecurityIssues   = 0;
            int closedSecurityIssues = 0;

            var issues = await Client.Issue.GetAllForRepository(purl.Namespace, purl.Name, filter);

            foreach (var issue in issues)
            {
                // filter out pull requests
                if (issue.Url.Contains("/pull") || issue.HtmlUrl.Contains("/pull"))
                {
                    continue;
                }

                //general issue status
                if (issue.State == ItemState.Open)
                {
                    openIssues++;
                }
                else if (issue.State == ItemState.Closed)
                {
                    closedIssues++;
                }

                // security status check within applicable fields
                var labels  = string.Join(",", issue.Labels.Select(l => l.Name));
                var content = issue.Title + issue.Body + labels;

                if (securityFlags.Any(s => content.Contains(s, StringComparison.InvariantCultureIgnoreCase)))
                {
                    if (issue.State == ItemState.Open)
                    {
                        openSecurityIssues++;
                    }
                    else if (issue.State == ItemState.Closed)
                    {
                        closedSecurityIssues++;
                    }
                }
            }
            double issueHealth = 0.0;

            if (openIssues + closedIssues > 0)
            {
                issueHealth = 30.0 * openIssues / (openIssues + closedIssues);
            }
            else
            {
                issueHealth = 50.0;
            }

            metrics.IssueHealth = issueHealth;

            double securityIssueHealth = 0.0;

            if (openSecurityIssues + closedSecurityIssues > 0)
            {
                securityIssueHealth = openSecurityIssues / (openSecurityIssues + closedSecurityIssues);
            }
            else
            {
                securityIssueHealth = 60.0;  // Lose a little credit if project never had a security issue
            }

            metrics.SecurityIssueHealth = securityIssueHealth;

            return;
        }