Skip to content

Commit 6a981b4

Browse files
committed
Add resource usage license metrics
Fixes #12
1 parent 3e7adaf commit 6a981b4

File tree

4 files changed

+182
-12
lines changed

4 files changed

+182
-12
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ Metrics
8686
| `azure_devops_stats_project_builds_wait` | stats | Build wait time per project, definition and result (summary) |
8787
| `azure_devops_stats_project_builds_duration` | stats | Build duration per project, definition and result (summary) |
8888
| `azure_devops_stats_project_release_duration` | stats | Release environment duration per project, definition, environment and result (summary) |
89-
| `azure_devops_resourceusage_build` | resourceusage | Usage of limited and paid Azure DevOps resources |
89+
| `azure_devops_resourceusage_build` | resourceusage | Usage of limited and paid Azure DevOps resources (build) |
90+
| `azure_devops_resourceusage_license` | resourceusage | Usage of limited and paid Azure DevOps resources (license) |
9091

9192

9293
Usage
@@ -108,22 +109,29 @@ Application Options:
108109
--scrape.time.pullrequest= Scrape time for pullrequest metrics (time.duration) [$SCRAPE_TIME_PULLREQUEST]
109110
--scrape.time.stats= Scrape time for stats metrics (time.duration) [$SCRAPE_TIME_STATS]
110111
--scrape.time.resourceusage= Scrape time for resourceusage metrics (time.duration) [$SCRAPE_TIME_RESOURCEUSAGE]
112+
--scrape.time.query= Scrape time for query results (time.duration) [$SCRAPE_TIME_QUERY]
111113
--scrape.time.live= Scrape time for live metrics (time.duration) (default: 30s) [$SCRAPE_TIME_LIVE]
112114
--stats.summary.maxage= Stats Summary metrics max age (time.duration) [$STATS_SUMMARY_MAX_AGE]
113115
--whitelist.project= Filter projects (UUIDs) [$AZURE_DEVOPS_FILTER_PROJECT]
114116
--blacklist.project= Filter projects (UUIDs) [$AZURE_DEVOPS_BLACKLIST_PROJECT]
115117
--whitelist.agentpool= Filter of agent pool (IDs) [$AZURE_DEVOPS_FILTER_AGENTPOOL]
116-
--whitelist.queries= Pairs of query and project UUIDs in the form: '<queryId>@<projectId>' [%AZURE_DEVOPS_QUERIES%]
118+
--list.query= Pairs of query and project UUIDs in the form: '<queryId>@<projectId>' [$AZURE_DEVOPS_QUERIES]
117119
--azuredevops.url= Azure DevOps url (empty if hosted by microsoft) [$AZURE_DEVOPS_URL]
118120
--azuredevops.access-token= Azure DevOps access token [$AZURE_DEVOPS_ACCESS_TOKEN]
119121
--azuredevops.organisation= Azure DevOps organization [$AZURE_DEVOPS_ORGANISATION]
120-
--azuredevops.apiversion= Azure DevOps API version (default: 5.0) [$AZURE_DEVOPS_APIVERSION]
122+
--azuredevops.apiversion= Azure DevOps API version (default: 5.1) [$AZURE_DEVOPS_APIVERSION]
121123
--request.concurrency= Number of concurrent requests against dev.azure.com (default: 10) [$REQUEST_CONCURRENCY]
122124
--request.retries= Number of retried requests against dev.azure.com (default: 3) [$REQUEST_RETRIES]
125+
--limit.builds-per-project= Limit builds per project (default: 100) [$LIMIT_BUILDS_PER_PROJECT]
123126
--limit.builds-per-definition= Limit builds per definition (default: 10) [$LIMIT_BUILDS_PER_DEFINITION]
127+
--limit.releases-per-project= Limit releases per project (default: 100) [$LIMIT_RELEASES_PER_PROJECT]
124128
--limit.releases-per-definition= Limit releases per definition (default: 100) [$LIMIT_RELEASES_PER_DEFINITION]
125129
--limit.deployments-per-definition= Limit deployments per definition (default: 100) [$LIMIT_DEPLOYMENTS_PER_DEFINITION]
126130
--limit.releasedefinitions-per-project= Limit builds per definition (default: 100) [$LIMIT_RELEASEDEFINITION_PER_PROJECT]
131+
--limit.build-history-duration= Time (time.Duration) how long the exporter should look back for builds (default: 48h)
132+
[$LIMIT_BUILD_HISTORY_DURATION]
133+
--limit.release-history-duration= Time (time.Duration) how long the exporter should look back for releases (default: 48h)
134+
[$LIMIT_RELEASE_HISTORY_DURATION]
127135
128136
Help Options:
129137
-h, --help Show this help message

azure-devops-client/resource_usage.go

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,59 @@ import (
66
"net/url"
77
)
88

9-
type ResourceUsageBuild struct {
10-
DistributedTaskAgents *int `json:"distributedTaskAgents"`
11-
PaidPrivateAgentSlots *int `json:"paidPrivateAgentSlots"`
12-
TotalUsage *int `json:"totalUsage"`
13-
XamlControllers *int `json:"xamlControllers"`
14-
}
9+
type (
10+
ResourceUsageBuild struct {
11+
DistributedTaskAgents *int `json:"distributedTaskAgents"`
12+
PaidPrivateAgentSlots *int `json:"paidPrivateAgentSlots"`
13+
TotalUsage *int `json:"totalUsage"`
14+
XamlControllers *int `json:"xamlControllers"`
15+
}
16+
17+
ResourceUsageAgent struct {
18+
Data struct {
19+
Provider struct {
20+
IncludeResourceLimitsSection bool `json:"includeResourceLimitsSection"`
21+
IncludeConcurrentJobsSection bool `json:"includeConcurrentJobsSection"`
22+
23+
ResourceUsages []ResourceUsageAgentUsageRow `json:"resourceUsages"`
24+
25+
TaskHubLicenseDetails struct {
26+
FreeLicenseCount *float64 `json:"freeLicenseCount"`
27+
FreeHostedLicenseCount *float64 `json:"freeHostedLicenseCount"`
28+
EnterpriseUsersCount *float64 `json:"enterpriseUsersCount"`
29+
PurchasedLicenseCount *float64 `json:"purchasedLicenseCount"`
30+
PurchasedHostedLicenseCount *float64 `json:"purchasedHostedLicenseCount"`
31+
HostedLicensesArePremium bool `json:"hostedLicensesArePremium"`
32+
TotalLicenseCount *float64 `json:"totalLicenseCount"`
33+
HasLicenseCountEverUpdated bool `json:"hasLicenseCountEverUpdated"`
34+
MsdnUsersCount *float64 `json:"msdnUsersCount"`
35+
HostedAgentMinutesFreeCount *float64 `json:"hostedAgentMinutesFreeCount"`
36+
HostedAgentMinutesUsedCount *float64 `json:"hostedAgentMinutesUsedCount"`
37+
FailedToReachAllProviders bool `json:"failedToReachAllProviders"`
38+
TotalPrivateLicenseCount *float64 `json:"totalPrivateLicenseCount"`
39+
TotalHostedLicenseCount *float64 `json:"totalHostedLicenseCount"`
40+
} `json:"taskHubLicenseDetails"`
41+
42+
} `json:"ms.vss-build-web.build-queue-hub-data-provider"`
43+
} `json:"data"`
44+
}
45+
46+
ResourceUsageAgentUsageRow struct {
47+
ResourceLimit struct {
48+
ResourceLimitsData struct {
49+
FreeCount string `json:"freeCount"`
50+
PurchasedCount string `json:"purchasedCount"`
51+
} `json:"resourceLimitsData"`
52+
53+
HostId string `json:"hostId"`
54+
ParallelismTag string `json:"parallelismTag"`
55+
IsHosted bool `json:"isHosted"`
56+
TotalCount float64 `json:"totalCount"`
57+
IsPremium bool `json:"IsPremium"`
58+
59+
} `json:"resourceLimit"`
60+
}
61+
)
1562

1663
func (c *AzureDevopsClient) GetResourceUsageBuild() (ret ResourceUsageBuild, error error) {
1764
defer c.concurrencyUnlock()
@@ -36,3 +83,33 @@ func (c *AzureDevopsClient) GetResourceUsageBuild() (ret ResourceUsageBuild, err
3683

3784
return
3885
}
86+
87+
func (c *AzureDevopsClient) GetResourceUsageAgent() (ret ResourceUsageAgent, error error) {
88+
defer c.concurrencyUnlock()
89+
c.concurrencyLock()
90+
91+
url := fmt.Sprintf(
92+
"/_apis/Contribution/dataProviders/query?api-version=%v",
93+
// FIXME: hardcoded api version
94+
url.QueryEscape("5.1-preview.1"),
95+
)
96+
97+
payload := `{"contributionIds": ["ms.vss-build-web.build-queue-hub-data-provider"]}`
98+
99+
req := c.rest().NewRequest()
100+
req.SetHeader("Content-Type", "application/json")
101+
req.SetBody(payload)
102+
response, err := req.Post(url)
103+
if err := c.checkResponse(response, err); err != nil {
104+
error = err
105+
return
106+
}
107+
108+
err = json.Unmarshal(response.Body(), &ret)
109+
if err != nil {
110+
error = err
111+
return
112+
}
113+
114+
return
115+
}

collector_structs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ func (m *MetricCollectorList) AddDuration(labels prometheus.Labels, value time.D
4444
m.list = append(m.list, MetricCollectorRow{labels: labels, value: value.Seconds()})
4545
}
4646

47+
func (m *MetricCollectorList) AddIfNotNil(labels prometheus.Labels, value *float64) {
48+
if value != nil {
49+
m.list = append(m.list, MetricCollectorRow{labels: labels, value: *value})
50+
}
51+
}
52+
4753
func (m *MetricCollectorList) AddIfNotZero(labels prometheus.Labels, value float64) {
4854
if value != 0 {
4955
m.list = append(m.list, MetricCollectorRow{labels: labels, value: value})

metrics_resourceusage.go

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ type MetricsCollectorResourceUsage struct {
99
CollectorProcessorGeneral
1010

1111
prometheus struct {
12-
resourceUsageBuild *prometheus.GaugeVec
12+
resourceUsageBuild *prometheus.GaugeVec
13+
resourceUsageLicense *prometheus.GaugeVec
1314
}
1415
}
1516

@@ -26,18 +27,96 @@ func (m *MetricsCollectorResourceUsage) Setup(collector *CollectorGeneral) {
2627
},
2728
)
2829

30+
31+
m.prometheus.resourceUsageLicense = prometheus.NewGaugeVec(
32+
prometheus.GaugeOpts{
33+
Name: "azure_devops_resourceusage_license",
34+
Help: "Azure DevOps resource usage for license informations",
35+
},
36+
[]string{
37+
"name",
38+
},
39+
)
40+
2941
prometheus.MustRegister(m.prometheus.resourceUsageBuild)
42+
prometheus.MustRegister(m.prometheus.resourceUsageLicense)
3043
}
3144

3245
func (m *MetricsCollectorResourceUsage) Reset() {
3346
m.prometheus.resourceUsageBuild.Reset()
47+
m.prometheus.resourceUsageLicense.Reset()
3448
}
3549

3650
func (m *MetricsCollectorResourceUsage) Collect(ctx context.Context, callback chan<- func()) {
37-
m.CollectResourceUsage(ctx, callback)
51+
m.CollectResourceUsageBuild(ctx, callback)
52+
m.CollectResourceUsageAgent(ctx, callback)
53+
}
54+
55+
func (m *MetricsCollectorResourceUsage) CollectResourceUsageAgent(ctx context.Context, callback chan<- func()) {
56+
resourceUsage, err := AzureDevopsClient.GetResourceUsageAgent()
57+
if err != nil {
58+
Logger.Errorf("call[GetResourceUsageAgent]: %v", err)
59+
return
60+
}
61+
62+
resourceUsageMetric := NewMetricCollectorList()
63+
64+
licenseDetails := resourceUsage.Data.Provider.TaskHubLicenseDetails
65+
66+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
67+
"name": "FreeLicenseCount",
68+
}, licenseDetails.FreeLicenseCount)
69+
70+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
71+
"name": "FreeHostedLicenseCount",
72+
}, licenseDetails.FreeHostedLicenseCount)
73+
74+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
75+
"name": "EnterpriseUsersCount",
76+
}, licenseDetails.EnterpriseUsersCount)
77+
78+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
79+
"name": "EnterpriseUsersCount",
80+
}, licenseDetails.EnterpriseUsersCount)
81+
82+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
83+
"name": "PurchasedHostedLicenseCount",
84+
}, licenseDetails.PurchasedHostedLicenseCount)
85+
86+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
87+
"name": "PurchasedHostedLicenseCount",
88+
}, licenseDetails.PurchasedHostedLicenseCount)
89+
90+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
91+
"name": "TotalLicenseCount",
92+
}, licenseDetails.TotalLicenseCount)
93+
94+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
95+
"name": "MsdnUsersCount",
96+
}, licenseDetails.MsdnUsersCount)
97+
98+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
99+
"name": "HostedAgentMinutesFreeCount",
100+
}, licenseDetails.HostedAgentMinutesFreeCount)
101+
102+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
103+
"name": "HostedAgentMinutesUsedCount",
104+
}, licenseDetails.HostedAgentMinutesUsedCount)
105+
106+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
107+
"name": "TotalPrivateLicenseCount",
108+
}, licenseDetails.TotalPrivateLicenseCount)
109+
110+
resourceUsageMetric.AddIfNotNil(prometheus.Labels{
111+
"name": "TotalHostedLicenseCount",
112+
}, licenseDetails.TotalHostedLicenseCount)
113+
114+
callback <- func() {
115+
resourceUsageMetric.GaugeSet(m.prometheus.resourceUsageLicense)
116+
}
38117
}
39118

40-
func (m *MetricsCollectorResourceUsage) CollectResourceUsage(ctx context.Context, callback chan<- func()) {
119+
func (m *MetricsCollectorResourceUsage) CollectResourceUsageBuild(ctx context.Context, callback chan<- func()) {
41120
resourceUsage, err := AzureDevopsClient.GetResourceUsageBuild()
42121
if err != nil {
43122
Logger.Errorf("call[GetResourceUsageBuild]: %v", err)

0 commit comments

Comments
 (0)