-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from apigee/issue41
generate org level reports #41
- Loading branch information
Showing
6 changed files
with
459 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// Copyright 2022 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package env | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/url" | ||
"os" | ||
"path" | ||
"strconv" | ||
"sync" | ||
"text/tabwriter" | ||
"time" | ||
|
||
"github.com/apigee/apigeecli/apiclient" | ||
"github.com/apigee/apigeecli/clilog" | ||
) | ||
|
||
const proxy_dimension = "apiproxy" | ||
const selection = "sum(message_count)" | ||
|
||
type report struct { | ||
Environments []environmentreport `json:"environments,omitempty"` | ||
} | ||
|
||
type environmentreport struct { | ||
Name string `json:"name,omitempty"` | ||
Dimensions []dimension `json:"dimensions,omitempty"` | ||
} | ||
|
||
type dimension struct { | ||
Name string `json:"name,omitempty"` | ||
Metrics []metric `json:"metrics,omitempty"` | ||
} | ||
|
||
type metric struct { | ||
Name string `json:"name,omitempty"` | ||
Values []string `json:"values,omitempty"` | ||
} | ||
|
||
var envAPICalls int | ||
var mu sync.Mutex | ||
|
||
func TotalAPICallsInMonthAsync(environment string, month int, year int, envDetails bool, wg *sync.WaitGroup) { | ||
defer wg.Done() | ||
var total int | ||
var err error | ||
|
||
if total, err = TotalAPICallsInMonth(environment, month, year); err != nil { | ||
clilog.Error.Println(err) | ||
return | ||
} | ||
syncCount(total) | ||
if envDetails { | ||
w := tabwriter.NewWriter(os.Stdout, 26, 4, 0, ' ', 0) | ||
fmt.Fprintf(w, "%s\t%d/%d\t%d", environment, month, year, total) | ||
fmt.Fprintln(w) | ||
w.Flush() | ||
} | ||
return | ||
} | ||
|
||
func TotalAPICallsInMonth(environment string, month int, year int) (total int, err error) { | ||
|
||
var apiCalls int | ||
var respBody []byte | ||
|
||
u, _ := url.Parse(apiclient.BaseURL) | ||
|
||
timeRange := fmt.Sprintf("%d/01/%d 00:00~%d/%d/%d 23:59", month, year, month, daysIn(time.Month(month), year), year) | ||
|
||
q := u.Query() | ||
q.Set("select", selection) | ||
q.Set("timeRange", timeRange) | ||
|
||
u.RawQuery = q.Encode() | ||
u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "environments", environment, "stats", proxy_dimension) | ||
|
||
if respBody, err = apiclient.HttpClient(apiclient.GetPrintOutput(), u.String()); err != nil { | ||
return -1, err | ||
} | ||
|
||
environmentReport := report{} | ||
|
||
if err = json.Unmarshal(respBody, &environmentReport); err != nil { | ||
return -1, err | ||
} | ||
|
||
for _, e := range environmentReport.Environments { | ||
for _, d := range e.Dimensions { | ||
for _, m := range d.Metrics { | ||
calls, _ := strconv.Atoi(m.Values[0]) | ||
apiCalls = apiCalls + calls | ||
} | ||
} | ||
} | ||
|
||
return apiCalls, nil | ||
} | ||
|
||
//GetEnvAPICalls | ||
func GetEnvAPICalls() int { | ||
return envAPICalls | ||
} | ||
|
||
func ResetEnvAPICalls() { | ||
mu.Lock() | ||
defer mu.Unlock() | ||
envAPICalls = 0 | ||
} | ||
|
||
// daysIn returns the number of days in a month for a given year. | ||
//source https://groups.google.com/g/golang-nuts/c/W-ezk71hioo | ||
func daysIn(m time.Month, year int) int { | ||
// This is equivalent to time.daysIn(m, year). | ||
return time.Date(year, m+1, 0, 0, 0, 0, 0, time.UTC).Day() | ||
} | ||
|
||
//syncCount synchronizes counting | ||
func syncCount(total int) { | ||
mu.Lock() | ||
defer mu.Unlock() | ||
envAPICalls = envAPICalls + total | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright 2022 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package orgs | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"sync" | ||
"time" | ||
|
||
"github.com/apigee/apigeecli/apiclient" | ||
"github.com/apigee/apigeecli/client/env" | ||
"github.com/apigee/apigeecli/clilog" | ||
) | ||
|
||
func TotalAPICallsInMonth(month int, year int, envDetails bool, conn int) (total int, err error) { | ||
|
||
var pwg sync.WaitGroup | ||
var envListBytes []byte | ||
var envList []string | ||
|
||
//ensure the count is reset to zero before calculating the next set | ||
defer env.ResetEnvAPICalls() | ||
|
||
apiclient.SetPrintOutput(false) | ||
|
||
if envListBytes, err = env.List(); err != nil { | ||
return -1, err | ||
} | ||
|
||
if err = json.Unmarshal(envListBytes, &envList); err != nil { | ||
return -1, err | ||
} | ||
|
||
numEntities := len(envList) | ||
clilog.Info.Printf("Found %d environments\n", numEntities) | ||
clilog.Info.Printf("Generate report with %d connections\n", conn) | ||
|
||
numOfLoops, remaining := numEntities/conn, numEntities%conn | ||
|
||
//ensure connections aren't greater than entities | ||
if conn > numEntities { | ||
conn = numEntities | ||
} | ||
|
||
start := 0 | ||
|
||
for i, end := 0, 0; i < numOfLoops; i++ { | ||
pwg.Add(1) | ||
end = (i * conn) + conn | ||
clilog.Info.Printf("Creating reports for a batch %d of environments\n", (i + 1)) | ||
go batchReport(envList[start:end], month, year, envDetails, &pwg) | ||
start = end | ||
pwg.Wait() | ||
} | ||
|
||
if remaining > 0 { | ||
pwg.Add(1) | ||
clilog.Info.Printf("Creating reports for remaining %d environments\n", remaining) | ||
go batchReport(envList[start:numEntities], month, year, envDetails, &pwg) | ||
pwg.Wait() | ||
} | ||
|
||
apiclient.SetPrintOutput(true) | ||
|
||
return env.GetEnvAPICalls(), nil | ||
} | ||
|
||
func batchReport(envList []string, month int, year int, envDetails bool, pwg *sync.WaitGroup) { | ||
defer pwg.Done() | ||
//batch workgroup | ||
var bwg sync.WaitGroup | ||
|
||
bwg.Add(len(envList)) | ||
|
||
for _, environment := range envList { | ||
go env.TotalAPICallsInMonthAsync(environment, month, year, envDetails, &bwg) | ||
} | ||
|
||
bwg.Wait() | ||
} | ||
|
||
func TotalAPICallsInYear(year int, envDetails bool, conn int) (total int, err error) { | ||
|
||
var monthlyTotal int | ||
|
||
t := time.Now() | ||
currentYear := t.Year() | ||
|
||
if year > currentYear { | ||
return -1, fmt.Errorf("Invalid year. Year cannot be greater than current year") | ||
} | ||
|
||
if currentYear == year { | ||
currentMonth := t.Month() | ||
for i := 1; i <= int(currentMonth); i++ { //run the loop only till the current month | ||
if monthlyTotal, err = TotalAPICallsInMonth(i, year, envDetails, conn); err != nil { | ||
return -1, err | ||
} | ||
total = total + monthlyTotal | ||
} | ||
} else { | ||
for i := 1; i <= 12; i++ { //run the loop for each month | ||
if monthlyTotal, err = TotalAPICallsInMonth(i, year, envDetails, conn); err != nil { | ||
return -1, err | ||
} | ||
total = total + monthlyTotal | ||
} | ||
} | ||
|
||
return total, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright 2022 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package org | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"text/tabwriter" | ||
"time" | ||
|
||
"github.com/apigee/apigeecli/apiclient" | ||
"github.com/apigee/apigeecli/client/orgs" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
//MonthlyCmd to get monthly usage | ||
var MonthlyCmd = &cobra.Command{ | ||
Use: "monthly", | ||
Short: "Report monthly usage for an Apigee Org", | ||
Long: "Report monthly usage for an Apigee Org", | ||
Args: func(cmd *cobra.Command, args []string) (err error) { | ||
return apiclient.SetApigeeOrg(org) | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) (err error) { | ||
var apiCalls int | ||
|
||
if _, err = time.Parse("1/2006", fmt.Sprintf("%d/%d", month, year)); err != nil { | ||
return | ||
} | ||
|
||
if envDetails { | ||
w := tabwriter.NewWriter(os.Stdout, 26, 4, 0, ' ', 0) | ||
fmt.Fprintln(w, "ENVIRONMENT\tMONTH\tAPI CALLS") | ||
fmt.Fprintln(w) | ||
w.Flush() | ||
} | ||
|
||
if apiCalls, err = orgs.TotalAPICallsInMonth(month, year, envDetails, conn); err != nil { | ||
return | ||
} | ||
|
||
if envDetails { | ||
fmt.Printf("\nSummary\n\n") | ||
} | ||
|
||
w := tabwriter.NewWriter(os.Stdout, 26, 4, 0, ' ', 0) | ||
fmt.Fprintln(w, "ORGANIATION\tMONTH\tAPI CALLS") | ||
fmt.Fprintf(w, "%s\t%d/%d\t%d\n", apiclient.GetApigeeOrg(), month, year, apiCalls) | ||
fmt.Fprintln(w) | ||
w.Flush() | ||
|
||
return | ||
}, | ||
} | ||
|
||
var month, year int | ||
var envDetails bool | ||
|
||
func init() { | ||
|
||
MonthlyCmd.Flags().IntVarP(&month, "month", "m", | ||
-1, "Month") | ||
MonthlyCmd.Flags().IntVarP(&year, "year", "y", | ||
-1, "Year") | ||
MonthlyCmd.Flags().BoolVarP(&envDetails, "env-details", "", | ||
false, "Print details of each environment") | ||
MonthlyCmd.Flags().IntVarP(&conn, "conn", "c", | ||
4, "Number of connections") | ||
MonthlyCmd.Flags().StringVarP(&org, "org", "o", | ||
"", "Apigee organization name") | ||
|
||
_ = MonthlyCmd.MarkFlagRequired("month") | ||
_ = MonthlyCmd.MarkFlagRequired("year") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright 2022 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package org | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
//ReportCmd to manage org reprots | ||
var ReportCmd = &cobra.Command{ | ||
Use: "reports", | ||
Aliases: []string{"orgs"}, | ||
Short: "Report Apigee Org Usage", | ||
Long: "Report Apigee Org Usage", | ||
} | ||
|
||
func init() { | ||
ReportCmd.AddCommand(MonthlyCmd) | ||
ReportCmd.AddCommand(YearlyCmd) | ||
} |
Oops, something went wrong.