Repository: cortexlabs/cortex (language: python)

Issue:
Revisit `cortex get` output in relation to grafana
`cortex get` performs the following operations:
* display a summary of API metrics
* make metrics available programmatically (important for batch)

`cortex get` is tightly coupled with prometheus. If prometheus goes down, the user currently has no way of getting any API state. In addition some of the metrics in `cortex get` are better consumed via grafana.

Come up with a design that displays the status of the APIs with as few dependencies on prometheus as possible and makes it easy for users to consume metrics in the intended way (either by grafana or programmatically).





Discussion / hints:

Fix patch:
diff --git a/cli/cmd/get.go b/cli/cmd/get.go
index c48512db9c..1b11b984a0 100644
--- a/cli/cmd/get.go
+++ b/cli/cmd/get.go
@@ -47,10 +47,6 @@ const (
 	_titleRequested   = "requested"
 	_titleFailed      = "failed"
 	_titleLastupdated = "last update"
-	_titleAvgRequest  = "avg request"
-	_title2XX         = "2XX"
-	_title4XX         = "4XX"
-	_title5XX         = "5XX"
 )
 
 var (
diff --git a/cli/cmd/lib_async_apis.go b/cli/cmd/lib_async_apis.go
index 9d852b1806..114c88bca8 100644
--- a/cli/cmd/lib_async_apis.go
+++ b/cli/cmd/lib_async_apis.go
@@ -62,8 +62,6 @@ func asyncAPIsTable(asyncAPIs []schema.APIResponse, envNames []string) table.Tab
 
 	var totalFailed int32
 	var totalStale int32
-	var total4XX int
-	var total5XX int
 
 	for i, asyncAPI := range asyncAPIs {
 		lastUpdated := time.Unix(asyncAPI.Spec.LastUpdated, 0)
@@ -76,19 +74,10 @@ func asyncAPIsTable(asyncAPIs []schema.APIResponse, envNames []string) table.Tab
 			asyncAPI.Status.Requested,
 			asyncAPI.Status.Updated.TotalFailed(),
 			libtime.SinceStr(&lastUpdated),
-			latencyStr(asyncAPI.Metrics),
-			code2XXStr(asyncAPI.Metrics),
-			code4XXStr(asyncAPI.Metrics),
-			code5XXStr(asyncAPI.Metrics),
 		})
 
 		totalFailed += asyncAPI.Status.Updated.TotalFailed()
 		totalStale += asyncAPI.Status.Stale.Ready
-
-		if asyncAPI.Metrics.NetworkStats != nil {
-			total4XX += asyncAPI.Metrics.NetworkStats.Code4XX
-			total5XX += asyncAPI.Metrics.NetworkStats.Code5XX
-		}
 	}
 
 	return table.Table{
@@ -101,10 +90,6 @@ func asyncAPIsTable(asyncAPIs []schema.APIResponse, envNames []string) table.Tab
 			{Title: _titleRequested},
 			{Title: _titleFailed, Hidden: totalFailed == 0},
 			{Title: _titleLastupdated},
-			{Title: _titleAvgRequest},
-			{Title: _title2XX},
-			{Title: _title4XX, Hidden: total4XX == 0},
-			{Title: _title5XX, Hidden: total5XX == 0},
 		},
 		Rows: rows,
 	}
diff --git a/cli/cmd/lib_batch_apis.go b/cli/cmd/lib_batch_apis.go
index b4a3e580af..272dbfa0fa 100644
--- a/cli/cmd/lib_batch_apis.go
+++ b/cli/cmd/lib_batch_apis.go
@@ -87,16 +87,7 @@ func batchAPITable(batchAPI schema.APIResponse) string {
 	if len(batchAPI.BatchJobStatuses) == 0 {
 		out = console.Bold("no submitted batch jobs\n")
 	} else {
-		totalFailed := 0
 		for _, job := range batchAPI.BatchJobStatuses {
-			succeeded := 0
-			failed := 0
-
-			if job.BatchMetrics != nil {
-				failed = job.BatchMetrics.Failed
-				succeeded = job.BatchMetrics.Succeeded
-				totalFailed += failed
-			}
 
 			jobEndTime := time.Now()
 			if job.EndTime != nil {
@@ -108,8 +99,7 @@ func batchAPITable(batchAPI schema.APIResponse) string {
 			jobRows = append(jobRows, []interface{}{
 				job.ID,
 				job.Status.Message(),
-				fmt.Sprintf("%d/%d", succeeded, job.TotalBatchCount),
-				failed,
+				job.TotalBatchCount,
 				job.StartTime.Format(_timeFormat),
 				duration,
 			})
@@ -119,8 +109,7 @@ func batchAPITable(batchAPI schema.APIResponse) string {
 			Headers: []table.Header{
 				{Title: "job id"},
 				{Title: "status"},
-				{Title: "progress"}, // (succeeded/total)
-				{Title: "failed attempts", Hidden: totalFailed == 0},
+				{Title: "total batches"},
 				{Title: "start time"},
 				{Title: "duration"},
 			},
@@ -189,14 +178,14 @@ func getBatchJob(env cliconfig.Environment, apiName string, jobID string) (strin
 	failed := "-"
 	avgTimePerBatch := "-"
 
-	if job.BatchMetrics != nil {
-		if job.BatchMetrics.AverageTimePerBatch != nil {
-			batchMetricsDuration := time.Duration(*job.BatchMetrics.AverageTimePerBatch*1000000000) * time.Nanosecond
+	if resp.Metrics != nil {
+		if resp.Metrics.AverageTimePerBatch != nil {
+			batchMetricsDuration := time.Duration(*resp.Metrics.AverageTimePerBatch*1000000000) * time.Nanosecond
 			avgTimePerBatch = batchMetricsDuration.Truncate(time.Millisecond).String()
 		}
 
-		succeeded = s.Int(job.BatchMetrics.Succeeded)
-		failed = s.Int(job.BatchMetrics.Failed)
+		succeeded = s.Int(resp.Metrics.Succeeded)
+		failed = s.Int(resp.Metrics.Failed)
 	}
 
 	t := table.Table{
diff --git a/cli/cmd/lib_realtime_apis.go b/cli/cmd/lib_realtime_apis.go
index 8f384bd658..be4316e0a8 100644
--- a/cli/cmd/lib_realtime_apis.go
+++ b/cli/cmd/lib_realtime_apis.go
@@ -17,17 +17,14 @@ limitations under the License.
 package cmd
 
 import (
-	"fmt"
 	"strings"
 	"time"
 
 	"github.com/cortexlabs/cortex/cli/types/cliconfig"
 	"github.com/cortexlabs/cortex/pkg/lib/console"
-	s "github.com/cortexlabs/cortex/pkg/lib/strings"
 	"github.com/cortexlabs/cortex/pkg/lib/table"
 	libtime "github.com/cortexlabs/cortex/pkg/lib/time"
 	"github.com/cortexlabs/cortex/pkg/operator/schema"
-	"github.com/cortexlabs/cortex/pkg/types/metrics"
 )
 
 func realtimeAPITable(realtimeAPI schema.APIResponse, env cliconfig.Environment) (string, error) {
@@ -61,8 +58,6 @@ func realtimeAPIsTable(realtimeAPIs []schema.APIResponse, envNames []string) tab
 
 	var totalFailed int32
 	var totalStale int32
-	var total4XX int
-	var total5XX int
 
 	for i, realtimeAPI := range realtimeAPIs {
 		lastUpdated := time.Unix(realtimeAPI.Spec.LastUpdated, 0)
@@ -75,19 +70,10 @@ func realtimeAPIsTable(realtimeAPIs []schema.APIResponse, envNames []string) tab
 			realtimeAPI.Status.Requested,
 			realtimeAPI.Status.Updated.TotalFailed(),
 			libtime.SinceStr(&lastUpdated),
-			latencyStr(realtimeAPI.Metrics),
-			code2XXStr(realtimeAPI.Metrics),
-			code4XXStr(realtimeAPI.Metrics),
-			code5XXStr(realtimeAPI.Metrics),
 		})
 
 		totalFailed += realtimeAPI.Status.Updated.TotalFailed()
 		totalStale += realtimeAPI.Status.Stale.Ready
-
-		if realtimeAPI.Metrics.NetworkStats != nil {
-			total4XX += realtimeAPI.Metrics.NetworkStats.Code4XX
-			total5XX += realtimeAPI.Metrics.NetworkStats.Code5XX
-		}
 	}
 
 	return table.Table{
@@ -100,42 +86,7 @@ func realtimeAPIsTable(realtimeAPIs []schema.APIResponse, envNames []string) tab
 			{Title: _titleRequested},
 			{Title: _titleFailed, Hidden: totalFailed == 0},
 			{Title: _titleLastupdated},
-			{Title: _titleAvgRequest},
-			{Title: _title2XX},
-			{Title: _title4XX, Hidden: total4XX == 0},
-			{Title: _title5XX, Hidden: total5XX == 0},
 		},
 		Rows: rows,
 	}
 }
-
-func latencyStr(metrics *metrics.Metrics) string {
-	if metrics.NetworkStats == nil || metrics.NetworkStats.Latency == nil {
-		return "-"
-	}
-	if *metrics.NetworkStats.Latency < 1000 {
-		return fmt.Sprintf("%.6g ms", *metrics.NetworkStats.Latency)
-	}
-	return fmt.Sprintf("%.6g s", (*metrics.NetworkStats.Latency)/1000)
-}
-
-func code2XXStr(metrics *metrics.Metrics) string {
-	if metrics.NetworkStats == nil || metrics.NetworkStats.Code2XX == 0 {
-		return "-"
-	}
-	return s.Int(metrics.NetworkStats.Code2XX)
-}
-
-func code4XXStr(metrics *metrics.Metrics) string {
-	if metrics.NetworkStats == nil || metrics.NetworkStats.Code4XX == 0 {
-		return "-"
-	}
-	return s.Int(metrics.NetworkStats.Code4XX)
-}
-
-func code5XXStr(metrics *metrics.Metrics) string {
-	if metrics.NetworkStats == nil || metrics.NetworkStats.Code5XX == 0 {
-		return "-"
-	}
-	return s.Int(metrics.NetworkStats.Code5XX)
-}
diff --git a/cli/cmd/lib_traffic_splitters.go b/cli/cmd/lib_traffic_splitters.go
index 214c3c094d..39c344038a 100644
--- a/cli/cmd/lib_traffic_splitters.go
+++ b/cli/cmd/lib_traffic_splitters.go
@@ -85,9 +85,6 @@ func trafficSplitTable(trafficSplitter schema.APIResponse, env cliconfig.Environ
 			apiRes.Status.Message(),
 			apiRes.Status.Requested,
 			libtime.SinceStr(&lastUpdated),
-			latencyStr(apiRes.Metrics),
-			code2XXStr(apiRes.Metrics),
-			code5XXStr(apiRes.Metrics),
 		})
 	}
 
@@ -99,9 +96,6 @@ func trafficSplitTable(trafficSplitter schema.APIResponse, env cliconfig.Environ
 			{Title: _titleStatus},
 			{Title: _titleRequested},
 			{Title: _titleLastupdated},
-			{Title: _titleAvgRequest},
-			{Title: _title2XX},
-			{Title: _title5XX},
 		},
 		Rows: rows,
 	}, nil
diff --git a/docs/workloads/batch/jobs.md b/docs/workloads/batch/jobs.md
index fd0977fa65..ff335a1571 100644
--- a/docs/workloads/batch/jobs.md
+++ b/docs/workloads/batch/jobs.md
@@ -195,11 +195,6 @@ RESPONSE:
         "sqs_url": <string>,
         "status": <string>,
         "batches_in_queue": <int>   # number of batches remaining in the queue
-        "batch_metrics": {
-            "succeeded": <int>      # number of succeeded batches
-            "failed": int           # number of failed attempts
-            "avg_time_per_batch": <float> (optional)  # average time spent working on a batch (only considers successful attempts)
-        },
         "worker_counts": {          # worker counts are only available while a job is running
             "pending": <int>,       # number of workers that are waiting for compute resources to be provisioned
             "initializing": <int>,  # number of workers that are initializing
@@ -215,6 +210,11 @@ RESPONSE:
     "endpoint": <string>
     "api_spec": {
         ...
+    },
+    "metrics": {
+        "succeeded": <int>      # number of succeeded batches
+        "failed": int           # number of failed attempts
+        "avg_time_per_batch": <float> (optional)  # average time spent working on a batch (only considers successful attempts)
     }
 }
 ```
diff --git a/pkg/crds/apis/batch/v1alpha1/batchjob_metrics.go b/pkg/crds/apis/batch/v1alpha1/batchjob_metrics.go
index f46ebe4da3..4930d0384a 100644
--- a/pkg/crds/apis/batch/v1alpha1/batchjob_metrics.go
+++ b/pkg/crds/apis/batch/v1alpha1/batchjob_metrics.go
@@ -34,7 +34,7 @@ const (
 )
 
 // GetMetrics retrieves the BatchJob metrics from prometheus
-func GetMetrics(promAPIv1 promv1.API, jobKey spec.JobKey, t time.Time) (metrics.BatchMetrics, error) {
+func GetMetrics(promAPIv1 promv1.API, jobKey spec.JobKey, t time.Time) (*metrics.BatchMetrics, error) {
 	var (
 		jobBatchesSucceeded float64
 		jobBatchesFailed    float64
@@ -59,10 +59,10 @@ func GetMetrics(promAPIv1 promv1.API, jobKey spec.JobKey, t time.Time) (metrics.
 		},
 	)
 	if err != nil {
-		return metrics.BatchMetrics{}, err
+		return nil, err
 	}
 
-	return metrics.BatchMetrics{
+	return &metrics.BatchMetrics{
 		Succeeded:           int(jobBatchesSucceeded),
 		Failed:              int(jobBatchesFailed),
 		AverageTimePerBatch: avgTimePerBatch,
diff --git a/pkg/crds/controllers/batch/batchjob_controller_helpers.go b/pkg/crds/controllers/batch/batchjob_controller_helpers.go
index e04d98524d..dd052dfc58 100644
--- a/pkg/crds/controllers/batch/batchjob_controller_helpers.go
+++ b/pkg/crds/controllers/batch/batchjob_controller_helpers.go
@@ -697,7 +697,7 @@ func getMetrics(r *BatchJobReconciler, batchJob batch.BatchJob) (metrics.BatchMe
 		return metrics.BatchMetrics{}, err
 	}
 
-	return jobMetrics, nil
+	return *jobMetrics, nil
 }
 
 func saveJobMetrics(r *BatchJobReconciler, batchJob batch.BatchJob) error {
diff --git a/pkg/operator/endpoints/get_batch_job.go b/pkg/operator/endpoints/get_batch_job.go
index ea78c7474e..1a7c50ac4a 100644
--- a/pkg/operator/endpoints/get_batch_job.go
+++ b/pkg/operator/endpoints/get_batch_job.go
@@ -18,12 +18,9 @@ package endpoints
 
 import (
 	"net/http"
-	"net/url"
 
-	"github.com/cortexlabs/cortex/pkg/operator/operator"
 	"github.com/cortexlabs/cortex/pkg/operator/resources"
 	"github.com/cortexlabs/cortex/pkg/operator/resources/job/batchapi"
-	"github.com/cortexlabs/cortex/pkg/operator/schema"
 	"github.com/cortexlabs/cortex/pkg/types/spec"
 	"github.com/cortexlabs/cortex/pkg/types/userconfig"
 	"github.com/gorilla/mux"
@@ -54,37 +51,11 @@ func GetBatchJob(w http.ResponseWriter, r *http.Request) {
 		Kind:    userconfig.BatchAPIKind,
 	}
 
-	jobStatus, err := batchapi.GetJobStatus(jobKey)
+	jobResponse, err := batchapi.GetJob(jobKey)
 	if err != nil {
 		respondError(w, r, err)
 		return
 	}
 
-	apiSpec, err := operator.DownloadAPISpec(jobStatus.APIName, jobStatus.APIID)
-	if err != nil {
-		respondError(w, r, err)
-		return
-	}
-
-	endpoint, err := operator.APIEndpoint(apiSpec)
-	if err != nil {
-		respondError(w, r, err)
-		return
-	}
-
-	parsedURL, err := url.Parse(endpoint)
-	if err != nil {
-		respondError(w, r, err)
-	}
-	q := parsedURL.Query()
-	q.Add("jobID", jobKey.ID)
-	parsedURL.RawQuery = q.Encode()
-
-	response := schema.BatchJobResponse{
-		JobStatus: *jobStatus,
-		APISpec:   *apiSpec,
-		Endpoint:  parsedURL.String(),
-	}
-
-	respondJSON(w, r, response)
+	respondJSON(w, r, jobResponse)
 }
diff --git a/pkg/operator/endpoints/logs_job.go b/pkg/operator/endpoints/logs_job.go
index 704bef376c..f1269c7ea5 100644
--- a/pkg/operator/endpoints/logs_job.go
+++ b/pkg/operator/endpoints/logs_job.go
@@ -81,7 +81,7 @@ func GetJobLogURL(w http.ResponseWriter, r *http.Request) {
 
 	switch deployedResource.Kind {
 	case userconfig.BatchAPIKind:
-		jobStatus, err := batchapi.GetJobStatus(spec.JobKey{
+		jobResponse, err := batchapi.GetJob(spec.JobKey{
 			ID:      jobID,
 			APIName: apiName,
 			Kind:    userconfig.BatchAPIKind,
@@ -90,7 +90,7 @@ func GetJobLogURL(w http.ResponseWriter, r *http.Request) {
 			respondError(w, r, err)
 			return
 		}
-		logURL, err := operator.BatchJobLogURL(apiName, *jobStatus)
+		logURL, err := operator.BatchJobLogURL(apiName, jobResponse.JobStatus)
 		if err != nil {
 			respondError(w, r, err)
 			return
diff --git a/pkg/operator/resources/asyncapi/api.go b/pkg/operator/resources/asyncapi/api.go
index 35848c5997..39cce27446 100644
--- a/pkg/operator/resources/asyncapi/api.go
+++ b/pkg/operator/resources/asyncapi/api.go
@@ -265,11 +265,6 @@ func GetAPIByName(deployedResource *operator.DeployedResource) ([]schema.APIResp
 		return nil, err
 	}
 
-	metrics, err := GetMetrics(*api)
-	if err != nil {
-		return nil, err
-	}
-
 	dashboardURL := pointer.String(getDashboardURL(api.Name))
 
 	return []schema.APIResponse{
@@ -278,7 +273,6 @@ func GetAPIByName(deployedResource *operator.DeployedResource) ([]schema.APIResp
 			Status:       status,
 			Endpoint:     apiEndpoint,
 			DashboardURL: dashboardURL,
-			Metrics:      metrics,
 		},
 	}, nil
 }
@@ -295,11 +289,6 @@ func GetAllAPIs(pods []kcore.Pod, deployments []kapps.Deployment) ([]schema.APIR
 		return nil, err
 	}
 
-	allMetrics, err := GetMultipleMetrics(apis)
-	if err != nil {
-		return nil, err
-	}
-
 	asyncAPIs := make([]schema.APIResponse, len(apis))
 
 	for i := range apis {
@@ -313,7 +302,6 @@ func GetAllAPIs(pods []kcore.Pod, deployments []kapps.Deployment) ([]schema.APIR
 			Spec:     api,
 			Status:   &statuses[i],
 			Endpoint: endpoint,
-			Metrics:  &allMetrics[i],
 		}
 	}
 
diff --git a/pkg/operator/resources/asyncapi/metrics.go b/pkg/operator/resources/asyncapi/metrics.go
deleted file mode 100644
index 35bcdebd05..0000000000
--- a/pkg/operator/resources/asyncapi/metrics.go
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
-Copyright 2021 Cortex Labs, Inc.
-
-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 asyncapi
-
-import (
-	"context"
-	"fmt"
-	"math"
-	"time"
-
-	"github.com/cortexlabs/cortex/pkg/config"
-	"github.com/cortexlabs/cortex/pkg/lib/errors"
-	"github.com/cortexlabs/cortex/pkg/lib/parallel"
-	"github.com/cortexlabs/cortex/pkg/types/metrics"
-	"github.com/cortexlabs/cortex/pkg/types/spec"
-	promv1 "github.com/prometheus/client_golang/api/prometheus/v1"
-	"github.com/prometheus/common/model"
-)
-
-const (
-	_metricsWindowHours    = 336 // 2 weeks
-	_metricsRequestTimeout = 10  // seconds
-)
-
-func GetMultipleMetrics(apis []spec.API) ([]metrics.Metrics, error) {
-	allMetrics := make([]metrics.Metrics, len(apis))
-	fns := make([]func() error, len(apis))
-
-	for i := range apis {
-		localIdx := i
-		api := apis[i]
-		fns[i] = func() error {
-			apiMetrics, err := GetMetrics(api)
-			if err != nil {
-				return err
-			}
-			allMetrics[localIdx] = *apiMetrics
-			return nil
-		}
-	}
-
-	if len(fns) > 0 {
-		err := parallel.RunFirstErr(fns[0], fns[1:]...)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return allMetrics, nil
-}
-
-func GetMetrics(api spec.API) (*metrics.Metrics, error) {
-	var (
-		reqCount       float64
-		avgLatency     *float64
-		statusCodes2XX float64
-		statusCodes4XX float64
-		statusCodes5XX float64
-	)
-
-	err := parallel.RunFirstErr(
-		func() error {
-			var err error
-			reqCount, err = getRequestCountMetric(config.Prometheus, api)
-			return err
-		},
-		func() error {
-			var err error
-			avgLatency, err = getAvgLatencyMetric(config.Prometheus, api)
-			return err
-		},
-		func() error {
-			var err error
-			statusCodes2XX, err = getStatusCode2XXMetric(config.Prometheus, api)
-			return err
-		},
-		func() error {
-			var err error
-			statusCodes4XX, err = getStatusCode4XXMetric(config.Prometheus, api)
-			return err
-		},
-		func() error {
-			var err error
-			statusCodes5XX, err = getStatusCode5XXMetric(config.Prometheus, api)
-			return err
-		},
-	)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &metrics.Metrics{
-		APIName: api.Name,
-		NetworkStats: &metrics.NetworkStats{
-			Latency: avgLatency,
-			Code2XX: int(statusCodes2XX),
-			Code4XX: int(statusCodes4XX),
-			Code5XX: int(statusCodes5XX),
-			Total:   int(reqCount),
-		},
-	}, nil
-}
-
-func getRequestCountMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"cortex_async_request_count{api_name=\"%s\", api_id=\"%s\"} > 0",
-		apiSpec.Name, apiSpec.ID,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	requestCount := float64(values[0].Value)
-	return requestCount, nil
-}
-
-func getAvgLatencyMetric(promAPIv1 promv1.API, apiSpec spec.API) (*float64, error) {
-	query := fmt.Sprintf(
-		"sum(rate(cortex_async_latency_sum{api_kind=\"AsyncAPI\",api_name=\"%s\", api_id=\"%s\"}[%dh])) by (api_name) "+
-			"/ sum(rate(cortex_async_latency_count{api_kind=\"AsyncAPI\",api_name=\"%s\", api_id=\"%s\"}[%dh])) by (api_name)",
-		apiSpec.Name, apiSpec.ID, _metricsWindowHours,
-		apiSpec.Name, apiSpec.ID, _metricsWindowHours,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return nil, err
-	}
-
-	if values.Len() == 0 {
-		return nil, nil
-	}
-
-	avgLatency := float64(values[0].Value)
-
-	if math.IsNaN(avgLatency) {
-		return nil, nil
-	}
-
-	avgLatency = avgLatency * 1000 // seconds to milliseconds
-
-	return &avgLatency, nil
-}
-
-func getStatusCode2XXMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"cortex_async_request_count{api_kind=\"AsyncAPI\", api_name=\"%s\", api_id=\"%s\", status_code=~\"2.+\"} > 0",
-		apiSpec.Name, apiSpec.ID,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	statusCodes2XX := float64(values[0].Value)
-	return statusCodes2XX, nil
-}
-
-func getStatusCode4XXMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"cortex_async_request_count{api_kind=\"AsyncAPI\", api_name=\"%s\", api_id=\"%s\", status_code=~\"4.+\"} > 0",
-		apiSpec.Name, apiSpec.ID,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	statusCodes4XX := float64(values[0].Value)
-	return statusCodes4XX, nil
-}
-
-func getStatusCode5XXMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"cortex_async_request_count{api_kind=\"AsyncAPI\", api_name=\"%s\", api_id=\"%s\", status_code=~\"5.+\"} > 0",
-		apiSpec.Name, apiSpec.ID,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	statusCodes5XX := float64(values[0].Value)
-	return statusCodes5XX, nil
-}
-
-func queryPrometheusVec(promAPIv1 promv1.API, query string) (model.Vector, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), _metricsRequestTimeout*time.Second)
-	defer cancel()
-
-	valuesQuery, _, err := promAPIv1.Query(ctx, query, time.Now())
-	if err != nil {
-		return nil, err
-	}
-
-	values, ok := valuesQuery.(model.Vector)
-	if !ok {
-		return nil, errors.ErrorUnexpected("failed to convert metric to vector")
-	}
-
-	return values, nil
-}
diff --git a/pkg/operator/resources/job/batchapi/job_status.go b/pkg/operator/resources/job/batchapi/job_status.go
index d188825a93..042b1d14d7 100644
--- a/pkg/operator/resources/job/batchapi/job_status.go
+++ b/pkg/operator/resources/job/batchapi/job_status.go
@@ -18,14 +18,17 @@ package batchapi
 
 import (
 	"context"
+	"net/url"
 	"time"
 
 	"github.com/cortexlabs/cortex/pkg/config"
 	batch "github.com/cortexlabs/cortex/pkg/crds/apis/batch/v1alpha1"
 	"github.com/cortexlabs/cortex/pkg/lib/aws"
 	"github.com/cortexlabs/cortex/pkg/lib/pointer"
+	"github.com/cortexlabs/cortex/pkg/lib/telemetry"
 	"github.com/cortexlabs/cortex/pkg/operator/operator"
 	"github.com/cortexlabs/cortex/pkg/operator/resources/job"
+	"github.com/cortexlabs/cortex/pkg/operator/schema"
 	"github.com/cortexlabs/cortex/pkg/types/metrics"
 	"github.com/cortexlabs/cortex/pkg/types/spec"
 	"github.com/cortexlabs/cortex/pkg/types/status"
@@ -35,23 +38,94 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-func GetJobStatus(jobKey spec.JobKey) (*status.BatchJobStatus, error) {
+func GetJob(jobKey spec.JobKey) (*schema.BatchJobResponse, error) {
 	ctx := context.Background()
 	var batchJob batch.BatchJob
+
 	err := config.K8s.Get(ctx, client.ObjectKey{Name: jobKey.ID, Namespace: config.K8s.Namespace}, &batchJob)
 	if err != nil && !kerrors.IsNotFound(err) {
 		return nil, err
 	}
-	if err == nil {
-		return getJobStatusFromBatchJob(batchJob)
+
+	if kerrors.IsNotFound(err) {
+		return getJobFromS3(jobKey)
 	}
 
+	return getJobFromCluster(batchJob)
+}
+
+func getJobFromS3(jobKey spec.JobKey) (*schema.BatchJobResponse, error) {
 	jobState, err := job.GetJobState(jobKey)
 	if err != nil {
 		return nil, err
 	}
 
-	return getJobStatusFromJobState(jobState)
+	jobStatus, err := getJobStatusFromJobState(jobState)
+	if err != nil {
+		return nil, err
+	}
+
+	var jobMetrics *metrics.BatchMetrics
+	if _, ok := jobState.LastUpdatedMap[spec.MetricsFileKey]; ok && jobState.Status.IsCompleted() {
+		jobMetrics, err = readMetricsFromS3(jobState.JobKey)
+		if err != nil {
+			telemetry.Error(err)
+		}
+	}
+
+	if jobMetrics == nil {
+		// try to get metrics from prometheus if they aren't available in S3 because there might be a delay
+		jobMetrics, err = batch.GetMetrics(config.Prometheus, jobStatus.JobKey, time.Now())
+		if err != nil {
+			telemetry.Error(err)
+		}
+	}
+
+	apiSpec, err := operator.DownloadAPISpec(jobStatus.APIName, jobStatus.APIID)
+	if err != nil {
+		return nil, err
+	}
+
+	endpoint, err := getJobEndpoint(apiSpec, jobKey)
+	if err != nil {
+		return nil, err
+	}
+
+	return &schema.BatchJobResponse{
+		APISpec:   *apiSpec,
+		JobStatus: *jobStatus,
+		Metrics:   jobMetrics,
+		Endpoint:  endpoint,
+	}, nil
+}
+
+func getJobFromCluster(batchJob batch.BatchJob) (*schema.BatchJobResponse, error) {
+	jobStatus, err := getJobStatusFromBatchJob(batchJob)
+	if err != nil {
+		return nil, err
+	}
+
+	jobMetrics, err := batch.GetMetrics(config.Prometheus, jobStatus.JobKey, time.Now())
+	if err != nil {
+		telemetry.Error(err)
+	}
+
+	apiSpec, err := operator.DownloadAPISpec(jobStatus.APIName, jobStatus.APIID)
+	if err != nil {
+		return nil, err
+	}
+
+	endpoint, err := getJobEndpoint(apiSpec, jobStatus.JobKey)
+	if err != nil {
+		return nil, err
+	}
+
+	return &schema.BatchJobResponse{
+		APISpec:   *apiSpec,
+		JobStatus: *jobStatus,
+		Metrics:   jobMetrics,
+		Endpoint:  endpoint,
+	}, nil
 }
 
 func getJobStatusFromBatchJob(batchJob batch.BatchJob) (*status.BatchJobStatus, error) {
@@ -117,12 +191,6 @@ func getJobStatusFromBatchJob(batchJob batch.BatchJob) (*status.BatchJobStatus,
 		}
 	}
 
-	jobMetrics, err := batch.GetMetrics(config.Prometheus, jobKey, time.Now())
-	if err != nil {
-		return nil, err
-	}
-
-	jobStatus.BatchMetrics = &jobMetrics
 	jobStatus.WorkerCounts = batchJob.Status.WorkerCounts
 
 	return &jobStatus, nil
@@ -142,23 +210,32 @@ func getJobStatusFromJobState(jobState *job.State) (*status.BatchJobStatus, erro
 		Status:   jobState.Status,
 	}
 
-	if _, ok := jobState.LastUpdatedMap[spec.MetricsFileKey]; ok && jobState.Status.IsCompleted() {
-		jobMetrics, err := readMetricsFromS3(jobKey)
-		if err != nil {
-			return nil, err
-		}
-		jobStatus.BatchMetrics = &jobMetrics
-	}
-
 	return &jobStatus, nil
 }
 
-func readMetricsFromS3(jobKey spec.JobKey) (metrics.BatchMetrics, error) {
+func readMetricsFromS3(jobKey spec.JobKey) (*metrics.BatchMetrics, error) {
 	s3Key := spec.JobMetricsKey(config.ClusterConfig.ClusterUID, userconfig.BatchAPIKind, jobKey.APIName, jobKey.ID)
 	batchMetrics := metrics.BatchMetrics{}
 	err := config.AWS.ReadJSONFromS3(&batchMetrics, config.ClusterConfig.Bucket, s3Key)
 	if err != nil {
-		return batchMetrics, err
+		return nil, err
 	}
-	return batchMetrics, nil
+	return &batchMetrics, nil
+}
+
+func getJobEndpoint(apiSpec *spec.API, jobKey spec.JobKey) (string, error) {
+	endpoint, err := operator.APIEndpoint(apiSpec)
+	if err != nil {
+		return "", err
+	}
+
+	parsedURL, err := url.Parse(endpoint)
+	if err != nil {
+		return "", err
+	}
+	q := parsedURL.Query()
+	q.Add("jobID", jobKey.ID)
+	parsedURL.RawQuery = q.Encode()
+
+	return parsedURL.String(), nil
 }
diff --git a/pkg/operator/resources/realtimeapi/api.go b/pkg/operator/resources/realtimeapi/api.go
index 5604b73bd7..256b253f8e 100644
--- a/pkg/operator/resources/realtimeapi/api.go
+++ b/pkg/operator/resources/realtimeapi/api.go
@@ -187,11 +187,6 @@ func GetAllAPIs(pods []kcore.Pod, deployments []kapps.Deployment) ([]schema.APIR
 		return nil, err
 	}
 
-	allMetrics, err := GetMultipleMetrics(apis)
-	if err != nil {
-		return nil, err
-	}
-
 	realtimeAPIs := make([]schema.APIResponse, len(apis))
 
 	for i := range apis {
@@ -204,7 +199,6 @@ func GetAllAPIs(pods []kcore.Pod, deployments []kapps.Deployment) ([]schema.APIR
 		realtimeAPIs[i] = schema.APIResponse{
 			Spec:     api,
 			Status:   &statuses[i],
-			Metrics:  &allMetrics[i],
 			Endpoint: endpoint,
 		}
 	}
@@ -235,11 +229,6 @@ func GetAPIByName(deployedResource *operator.DeployedResource) ([]schema.APIResp
 		return nil, err
 	}
 
-	metrics, err := GetMetrics(api)
-	if err != nil {
-		return nil, err
-	}
-
 	apiEndpoint, err := operator.APIEndpoint(api)
 	if err != nil {
 		return nil, err
@@ -251,7 +240,6 @@ func GetAPIByName(deployedResource *operator.DeployedResource) ([]schema.APIResp
 		{
 			Spec:         *api,
 			Status:       st,
-			Metrics:      metrics,
 			Endpoint:     apiEndpoint,
 			DashboardURL: dashboardURL,
 		},
diff --git a/pkg/operator/resources/realtimeapi/metrics.go b/pkg/operator/resources/realtimeapi/metrics.go
deleted file mode 100644
index c639a9a681..0000000000
--- a/pkg/operator/resources/realtimeapi/metrics.go
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
-Copyright 2021 Cortex Labs, Inc.
-
-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 realtimeapi
-
-import (
-	"context"
-	"fmt"
-	"math"
-	"time"
-
-	"github.com/cortexlabs/cortex/pkg/config"
-	"github.com/cortexlabs/cortex/pkg/lib/errors"
-	"github.com/cortexlabs/cortex/pkg/lib/parallel"
-	"github.com/cortexlabs/cortex/pkg/types/metrics"
-	"github.com/cortexlabs/cortex/pkg/types/spec"
-	promv1 "github.com/prometheus/client_golang/api/prometheus/v1"
-	"github.com/prometheus/common/model"
-)
-
-const (
-	_metricsWindowHours    = 336 // 2 weeks
-	_metricsRequestTimeout = 10  // seconds
-)
-
-func GetMultipleMetrics(apis []spec.API) ([]metrics.Metrics, error) {
-	allMetrics := make([]metrics.Metrics, len(apis))
-	fns := make([]func() error, len(apis))
-
-	for i := range apis {
-		localIdx := i
-		api := apis[i]
-		fns[i] = func() error {
-			apiMetrics, err := GetMetrics(&api)
-			if err != nil {
-				return err
-			}
-			allMetrics[localIdx] = *apiMetrics
-			return nil
-		}
-	}
-
-	if len(fns) > 0 {
-		err := parallel.RunFirstErr(fns[0], fns[1:]...)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return allMetrics, nil
-}
-
-func GetMetrics(api *spec.API) (*metrics.Metrics, error) {
-	var (
-		reqCount       float64
-		avgLatency     *float64
-		statusCodes2XX float64
-		statusCodes4XX float64
-		statusCodes5XX float64
-	)
-
-	err := parallel.RunFirstErr(
-		func() error {
-			var err error
-			reqCount, err = getRequestCountMetric(config.Prometheus, *api)
-			return err
-		},
-		func() error {
-			var err error
-			avgLatency, err = getAvgLatencyMetric(config.Prometheus, *api)
-			return err
-		},
-		func() error {
-			var err error
-			statusCodes2XX, err = getStatusCode2XXMetric(config.Prometheus, *api)
-			return err
-		},
-		func() error {
-			var err error
-			statusCodes4XX, err = getStatusCode4XXMetric(config.Prometheus, *api)
-			return err
-		},
-		func() error {
-			var err error
-			statusCodes5XX, err = getStatusCode5XXMetric(config.Prometheus, *api)
-			return err
-		},
-	)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &metrics.Metrics{
-		APIName: api.Name,
-		NetworkStats: &metrics.NetworkStats{
-			Latency: avgLatency,
-			Code2XX: int(statusCodes2XX),
-			Code4XX: int(statusCodes4XX),
-			Code5XX: int(statusCodes5XX),
-			Total:   int(reqCount),
-		},
-	}, nil
-}
-
-func getRequestCountMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"istio_requests_total{destination_service=~\"api-%s.+\"} > 0",
-		apiSpec.Name,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	requestCount := float64(values[0].Value)
-	return requestCount, nil
-}
-
-func getAvgLatencyMetric(promAPIv1 promv1.API, apiSpec spec.API) (*float64, error) {
-	query := fmt.Sprintf(
-		"sum(rate(istio_request_duration_milliseconds_sum{destination_service=~\"api-%s.+\"}[%dh])) by (destination_service) "+
-			"/ sum(rate(istio_request_duration_milliseconds_count{destination_service=~\"api-%s.+\"}[%dh])) by (destination_service)",
-		apiSpec.Name, _metricsWindowHours,
-		apiSpec.Name, _metricsWindowHours,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return nil, err
-	}
-
-	if values.Len() == 0 {
-		return nil, nil
-	}
-
-	avgLatency := float64(values[0].Value)
-
-	if math.IsNaN(avgLatency) {
-		return nil, nil
-	}
-	return &avgLatency, nil
-}
-
-func getStatusCode2XXMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"istio_requests_total{destination_service=~\"api-%s.+\", response_code=~\"2.*\"} > 0",
-		apiSpec.Name,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	statusCodes2XX := float64(values[0].Value)
-	return statusCodes2XX, nil
-}
-
-func getStatusCode4XXMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"istio_requests_total{destination_service=~\"api-%s.+\", response_code=~\"4.*\"} > 0",
-		apiSpec.Name,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	statusCodes4XX := float64(values[0].Value)
-	return statusCodes4XX, nil
-}
-
-func getStatusCode5XXMetric(promAPIv1 promv1.API, apiSpec spec.API) (float64, error) {
-	query := fmt.Sprintf(
-		"istio_requests_total{destination_service=~\"api-%s.+\", response_code=~\"5.*\"} > 0",
-		apiSpec.Name,
-	)
-
-	values, err := queryPrometheusVec(promAPIv1, query)
-	if err != nil {
-		return 0, err
-	}
-
-	if values.Len() == 0 {
-		return 0, nil
-	}
-
-	statusCodes5XX := float64(values[0].Value)
-	return statusCodes5XX, nil
-}
-
-func queryPrometheusVec(promAPIv1 promv1.API, query string) (model.Vector, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), _metricsRequestTimeout*time.Second)
-	defer cancel()
-
-	valuesQuery, _, err := promAPIv1.Query(ctx, query, time.Now())
-	if err != nil {
-		return nil, err
-	}
-
-	values, ok := valuesQuery.(model.Vector)
-	if !ok {
-		return nil, errors.ErrorUnexpected("failed to convert metric to vector")
-	}
-
-	return values, nil
-}
diff --git a/pkg/operator/schema/schema.go b/pkg/operator/schema/schema.go
index d86073e4e7..eff68701ee 100644
--- a/pkg/operator/schema/schema.go
+++ b/pkg/operator/schema/schema.go
@@ -58,7 +58,6 @@ type DeployResult struct {
 type APIResponse struct {
 	Spec             spec.API                `json:"spec"`
 	Status           *status.Status          `json:"status,omitempty"`
-	Metrics          *metrics.Metrics        `json:"metrics,omitempty"`
 	Endpoint         string                  `json:"endpoint"`
 	DashboardURL     *string                 `json:"dashboard_url,omitempty"`
 	BatchJobStatuses []status.BatchJobStatus `json:"batch_job_statuses,omitempty"`
@@ -73,6 +72,7 @@ type LogResponse struct {
 type BatchJobResponse struct {
 	APISpec   spec.API              `json:"api_spec"`
 	JobStatus status.BatchJobStatus `json:"job_status"`
+	Metrics   *metrics.BatchMetrics `json:"metrics,omitempty"`
 	Endpoint  string                `json:"endpoint"`
 }
 
diff --git a/pkg/types/metrics/batch_metrics.go b/pkg/types/metrics/batch_metrics.go
index e253903da4..d15da0da7b 100644
--- a/pkg/types/metrics/batch_metrics.go
+++ b/pkg/types/metrics/batch_metrics.go
@@ -16,6 +16,11 @@ limitations under the License.
 
 package metrics
 
+import (
+	"github.com/cortexlabs/cortex/pkg/lib/pointer"
+	"github.com/cortexlabs/cortex/pkg/lib/slices"
+)
+
 type BatchMetrics struct {
 	Succeeded           int      `json:"succeeded"`
 	Failed              int      `json:"failed"`
@@ -34,3 +39,11 @@ func (batchMetrics *BatchMetrics) MergeInPlace(right BatchMetrics) {
 	batchMetrics.Succeeded = batchMetrics.Succeeded + right.Succeeded
 	batchMetrics.Failed = batchMetrics.Failed + right.Failed
 }
+
+func mergeAvg(left *float64, leftCount int, right *float64, rightCount int) *float64 {
+	leftCountFloat64Ptr := pointer.Float64(float64(leftCount))
+	rightCountFloat64Ptr := pointer.Float64(float64(rightCount))
+
+	avg, _ := slices.Float64PtrAvg([]*float64{left, right}, []*float64{leftCountFloat64Ptr, rightCountFloat64Ptr})
+	return avg
+}
diff --git a/pkg/types/metrics/metrics.go b/pkg/types/metrics/metrics.go
deleted file mode 100644
index c3078c40ec..0000000000
--- a/pkg/types/metrics/metrics.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-Copyright 2021 Cortex Labs, Inc.
-
-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 metrics
-
-import (
-	"github.com/cortexlabs/cortex/pkg/lib/pointer"
-	"github.com/cortexlabs/cortex/pkg/lib/slices"
-)
-
-type Metrics struct {
-	APIName      string        `json:"api_name"`
-	NetworkStats *NetworkStats `json:"network_stats"`
-}
-
-func (left Metrics) Merge(right Metrics) Metrics {
-	var mergedNetworkStats *NetworkStats
-	switch {
-	case left.NetworkStats != nil && right.NetworkStats != nil:
-		merged := (*left.NetworkStats).Merge(*right.NetworkStats)
-		mergedNetworkStats = &merged
-	case left.NetworkStats != nil:
-		mergedNetworkStats = left.NetworkStats
-	case right.NetworkStats != nil:
-		mergedNetworkStats = right.NetworkStats
-	}
-
-	return Metrics{
-		NetworkStats: mergedNetworkStats,
-	}
-}
-
-type NetworkStats struct {
-	Latency *float64 `json:"latency"`
-	Code2XX int      `json:"code_2xx"`
-	Code4XX int      `json:"code_4xx"`
-	Code5XX int      `json:"code_5xx"`
-	Total   int      `json:"total"`
-}
-
-func (left NetworkStats) Merge(right NetworkStats) NetworkStats {
-	return NetworkStats{
-		Latency: mergeAvg(left.Latency, left.Total, right.Latency, right.Total),
-		Code2XX: left.Code2XX + right.Code2XX,
-		Code4XX: left.Code4XX + right.Code4XX,
-		Code5XX: left.Code5XX + right.Code5XX,
-		Total:   left.Total + right.Total,
-	}
-}
-
-func mergeAvg(left *float64, leftCount int, right *float64, rightCount int) *float64 {
-	leftCountFloat64Ptr := pointer.Float64(float64(leftCount))
-	rightCountFloat64Ptr := pointer.Float64(float64(rightCount))
-
-	avg, _ := slices.Float64PtrAvg([]*float64{left, right}, []*float64{leftCountFloat64Ptr, rightCountFloat64Ptr})
-	return avg
-}
diff --git a/pkg/types/status/job_status.go b/pkg/types/status/job_status.go
index 13da49eeed..eb299831ba 100644
--- a/pkg/types/status/job_status.go
+++ b/pkg/types/status/job_status.go
@@ -19,17 +19,15 @@ package status
 import (
 	"time"
 
-	"github.com/cortexlabs/cortex/pkg/types/metrics"
 	"github.com/cortexlabs/cortex/pkg/types/spec"
 )
 
 type BatchJobStatus struct {
 	spec.BatchJob
-	Status         JobCode               `json:"status"`
-	EndTime        *time.Time            `json:"end_time,omitempty"`
-	BatchesInQueue int                   `json:"batches_in_queue"`
-	BatchMetrics   *metrics.BatchMetrics `json:"batch_metrics,omitempty"`
-	WorkerCounts   *WorkerCounts         `json:"worker_counts,omitempty"`
+	Status         JobCode       `json:"status"`
+	EndTime        *time.Time    `json:"end_time,omitempty"`
+	BatchesInQueue int           `json:"batches_in_queue"`
+	WorkerCounts   *WorkerCounts `json:"worker_counts,omitempty"`
 }
 
 type TaskJobStatus struct {


Test patch:
diff --git a/pkg/types/metrics/metrics_test.go b/pkg/types/metrics/metrics_test.go
index bb75732e12..210545c3c5 100644
--- a/pkg/types/metrics/metrics_test.go
+++ b/pkg/types/metrics/metrics_test.go
@@ -41,58 +41,3 @@ func TestMergeAvg(t *testing.T) {
 	require.Equal(t, float64(1.25), *mergeAvg(pointer.Float64(1), 3, pointer.Float64(2), 1))
 	require.Equal(t, float64(1.25), *mergeAvg(pointer.Float64(2), 1, pointer.Float64(1), 3))
 }
-
-func TestNetworkStatsMerge(t *testing.T) {
-	require.Equal(t, NetworkStats{}, NetworkStats{}.Merge(NetworkStats{}))
-
-	right := NetworkStats{
-		Code2XX: 3,
-		Code4XX: 4,
-		Code5XX: 5,
-		Latency: pointer.Float64(30),
-		Total:   12,
-	}
-
-	left := NetworkStats{
-		Code2XX: 1,
-		Code4XX: 3,
-		Code5XX: 4,
-		Latency: pointer.Float64(5),
-		Total:   8,
-	}
-
-	merged := NetworkStats{
-		Code2XX: 4,
-		Code4XX: 7,
-		Code5XX: 9,
-		Latency: pointer.Float64(20),
-		Total:   20,
-	}
-
-	require.Equal(t, merged, left.Merge(right))
-	require.Equal(t, merged, right.Merge(left))
-}
-
-func TestAPIMetricsMerge(t *testing.T) {
-	require.Equal(t, Metrics{}, Metrics{}.Merge(Metrics{}))
-
-	networkStats := NetworkStats{
-		Code2XX: 3,
-		Code4XX: 4,
-		Code5XX: 5,
-		Latency: pointer.Float64(30),
-		Total:   12,
-	}
-
-	mergedNetworkStats := networkStats.Merge(networkStats)
-
-	mergedAPIMetrics := Metrics{
-		NetworkStats: &mergedNetworkStats,
-	}
-
-	apiMetrics := Metrics{
-		NetworkStats: &networkStats,
-	}
-
-	require.Equal(t, mergedAPIMetrics, apiMetrics.Merge(apiMetrics))
-}
diff --git a/test/e2e/e2e/tests.py b/test/e2e/e2e/tests.py
index 8c79f3aa10..49ef113037 100644
--- a/test/e2e/e2e/tests.py
+++ b/test/e2e/e2e/tests.py
@@ -553,18 +553,6 @@ def test_load_realtime(
             client=client, api_names=[api_name], timeout=deploy_timeout
         ), f"api {api_name} not ready"
 
-        network_stats = client.get_api(api_name)["metrics"]["network_stats"]
-        offset_2xx = network_stats["code_2xx"]
-        offset_4xx = network_stats["code_4xx"]
-        offset_5xx = network_stats["code_5xx"]
-
-        if offset_2xx is None:
-            offset_2xx = 0
-        if offset_4xx is None:
-            offset_4xx = 0
-        if offset_5xx is None:
-            offset_5xx = 0
-
         # give the APIs some time to prevent getting high latency spikes in the beginning
         time.sleep(5)
 
@@ -582,30 +570,12 @@ def test_load_realtime(
         )
 
         while not request_stopper.is_set():
-            api_info = client.get_api(api_name)
-            network_stats = api_info["metrics"]["network_stats"]
-
-            assert (
-                network_stats["code_4xx"] - offset_4xx == 0
-            ), f"detected 4xx response codes ({network_stats['code_4xx'] - offset_4xx}) in cortex get"
-            assert (
-                network_stats["code_5xx"] - offset_5xx == 0
-            ), f"detected 5xx response codes ({network_stats['code_5xx'] - offset_5xx}) in cortex get"
-
             # check if the requesting threads are still healthy
             # if not, they'll raise an exception
             check_futures_healthy(threads_futures)
 
             # don't stress the CPU too hard
             time.sleep(1)
-
-        printer(
-            f"verifying number of processed requests ({total_requests}, with an offset of {offset_2xx}) using the client"
-        )
-        assert api_requests(
-            client, api_name, total_requests + offset_2xx, timeout=status_code_timeout
-        ), f"the number of 2xx response codes for api {api_name} doesn't match the expected number {total_requests}"
-
     except:
         # best effort
         failed = True
@@ -831,7 +801,7 @@ def test_load_batch(
             assert (
                 job_status["batches_in_queue"] == 0
             ), f"there are still batches in queue ({job_status['batches_in_queue']}) for job ID {job_id}"
-            assert job_status["batch_metrics"]["succeeded"] == math.ceil(items_per_job / batch_size)
+            assert job_spec["metrics"]["succeeded"] == math.ceil(items_per_job / batch_size)
 
             num_objects = 0
             for page in paginator.paginate(Bucket=bucket, Prefix=os.path.join(key, job_id)):