Skip to content

Commit

Permalink
Merge pull request #42 from apigee/issue41
Browse files Browse the repository at this point in the history
generate org level reports #41
  • Loading branch information
srinandan authored Jul 29, 2022
2 parents 05b04ab + e3904d3 commit 05010ab
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 0 deletions.
137 changes: 137 additions & 0 deletions client/env/reports.go
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
}
124 changes: 124 additions & 0 deletions client/orgs/reports.go
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
}
1 change: 1 addition & 0 deletions cmd/org/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ func init() {
Cmd.AddCommand(ImportCmd)
Cmd.AddCommand(UpdateCmd)
Cmd.AddCommand(SetAddonCmd)
Cmd.AddCommand(ReportCmd)
}
86 changes: 86 additions & 0 deletions cmd/org/reportmonthly.go
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")
}
32 changes: 32 additions & 0 deletions cmd/org/reports.go
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)
}
Loading

0 comments on commit 05010ab

Please sign in to comment.