Skip to content

Commit

Permalink
Merge branch 'master' into raaj-patel/create_job_error
Browse files Browse the repository at this point in the history
  • Loading branch information
richscott committed Oct 27, 2023
2 parents 80e6318 + 0ac8eda commit 5b5fd9e
Show file tree
Hide file tree
Showing 32 changed files with 2,406 additions and 831 deletions.
150 changes: 150 additions & 0 deletions cmd/simulator/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package cmd

import (
"github.com/spf13/cobra"
"golang.org/x/exp/maps"

"github.com/armadaproject/armada/internal/common/armadacontext"
"github.com/armadaproject/armada/internal/common/util"
"github.com/armadaproject/armada/internal/scheduler/simulator"
"github.com/armadaproject/armada/pkg/armadaevents"
)

func RootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "Simulate",
Short: "Simulate running jobs on Armada.",
RunE: runSimulations,
}
// cmd.Flags().BoolP("verbose", "v", false, "Log detailed output to console.")
cmd.Flags().String("clusters", "", "Glob pattern specifying cluster configurations to simulate.")
cmd.Flags().String("workloads", "", "Glob pattern specifying workloads to simulate.")
cmd.Flags().String("configs", "", "Glob pattern specifying scheduler configurations to simulate.")
cmd.Flags().Bool("showSchedulerLogs", false, "Show scheduler logs.")
cmd.Flags().Int("logInterval", 0, "Log summary statistics every this many events. Disabled if 0.")
return cmd
}

func runSimulations(cmd *cobra.Command, args []string) error {
// Get command-line arguments.
clusterPattern, err := cmd.Flags().GetString("clusters")
if err != nil {
return err
}
workloadPattern, err := cmd.Flags().GetString("workloads")
if err != nil {
return err
}
configPattern, err := cmd.Flags().GetString("configs")
if err != nil {
return err
}
showSchedulerLogs, err := cmd.Flags().GetBool("showSchedulerLogs")
if err != nil {
return err
}
logInterval, err := cmd.Flags().GetInt("logInterval")
if err != nil {
return err
}

// Load test specs. and config.
clusterSpecs, err := simulator.ClusterSpecsFromPattern(clusterPattern)
if err != nil {
return err
}
workloadSpecs, err := simulator.WorkloadsFromPattern(workloadPattern)
if err != nil {
return err
}
schedulingConfigsByFilePath, err := simulator.SchedulingConfigsByFilePathFromPattern(configPattern)
if err != nil {
return err
}

ctx := armadacontext.Background()
ctx.Info("Armada simulator")
ctx.Infof("ClusterSpecs: %v", util.Map(clusterSpecs, func(clusperSpec *simulator.ClusterSpec) string { return clusperSpec.Name }))
ctx.Infof("WorkloadSpecs: %v", util.Map(workloadSpecs, func(workloadSpec *simulator.WorkloadSpec) string { return workloadSpec.Name }))
ctx.Infof("SchedulingConfigs: %v", maps.Keys(schedulingConfigsByFilePath))

// Setup a simulator for each combination of (clusterSpec, workloadSpec, schedulingConfig).
simulators := make([]*simulator.Simulator, 0)
metricsCollectors := make([]*simulator.MetricsCollector, 0)
eventSequenceChannels := make([]<-chan *armadaevents.EventSequence, 0)
schedulingConfigPaths := make([]string, 0)
for _, clusterSpec := range clusterSpecs {
for _, workloadSpec := range workloadSpecs {
for schedulingConfigPath, schedulingConfig := range schedulingConfigsByFilePath {
if s, err := simulator.NewSimulator(clusterSpec, workloadSpec, schedulingConfig); err != nil {
return err
} else {
if !showSchedulerLogs {
s.SuppressSchedulerLogs = true
} else {
ctx.Info("Showing scheduler logs")
}
simulators = append(simulators, s)
mc := simulator.NewMetricsCollector(s.Output())
mc.LogSummaryInterval = logInterval
metricsCollectors = append(metricsCollectors, mc)
eventSequenceChannels = append(eventSequenceChannels, s.Output())
schedulingConfigPaths = append(schedulingConfigPaths, schedulingConfigPath)
}
}
}
}

// Run simulators.
g, ctx := armadacontext.ErrGroup(ctx)
for _, s := range simulators {
s := s
g.Go(func() error {
return s.Run(ctx)
})
}

// Log events to stdout.
for _, c := range eventSequenceChannels {
c := c
g.Go(func() error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case eventSequence, ok := <-c:
if !ok {
return nil
}
ctx.Debug(*eventSequence.Events[0].Created, simulator.EventSequenceSummary(eventSequence))
}
}
})
}

// Run metric collectors.
for _, mc := range metricsCollectors {
mc := mc
g.Go(func() error {
return mc.Run(ctx)
})
}

// Wait for simulations to complete.
if err := g.Wait(); err != nil {
return err
}

// Log overall statistics.
for i, mc := range metricsCollectors {
s := simulators[i]
schedulingConfigPath := schedulingConfigPaths[i]
ctx.Infof("Simulation result")
ctx.Infof("ClusterSpec: %s", s.ClusterSpec.Name)
ctx.Infof("WorkloadSpec: %s", s.WorkloadSpec.Name)
ctx.Infof("SchedulingConfig: %s", schedulingConfigPath)
ctx.Info(mc.String())
}

return nil
}
13 changes: 13 additions & 0 deletions cmd/simulator/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"os"

"github.com/armadaproject/armada/cmd/simulator/cmd"
)

func main() {
if err := cmd.RootCmd().Execute(); err != nil {
os.Exit(1)
}
}
2 changes: 1 addition & 1 deletion cmd/testsuite/cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func testCmdRunE(app *testsuite.App) func(cmd *cobra.Command, args []string) err
app.Params.PrometheusPushGatewayJobName = prometheusPushgatewayJobName

// Create a context that is cancelled on SIGINT/SIGTERM.
// Ensures test jobs are cancelled on ctrl-C.
// Ensures test jobs are cancelled on ctrl-c.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stopSignal := make(chan os.Signal, 1)
Expand Down
1 change: 1 addition & 0 deletions internal/lookout/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export function App(props: AppProps): JSX.Element {
logService={props.v2LogService}
cordonService={props.v2CordonService}
debug={props.debugEnabled}
autoRefreshMs={props.jobsAutoRefreshMs}
/>
}
/>
Expand Down
2 changes: 1 addition & 1 deletion internal/lookout/ui/src/components/AutoRefreshToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function AutoRefreshToggle(props: AutoRefreshToggle) {
color="primary"
/>
}
label="Auto-refresh"
label="Auto refresh"
labelPlacement="start"
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion internal/lookout/ui/src/components/job-sets/JobSets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function JobSets(props: JobSetsProps) {
/>
</div>
<div className="job-sets-field">
<Tooltip title="Only display Queued, Pending or Running">
<Tooltip title="Only display job sets with at least one active job.">
<FormControlLabel
control={
<Checkbox
Expand Down
35 changes: 22 additions & 13 deletions internal/lookout/ui/src/components/lookoutV2/JobsTableActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { memo, useCallback, useMemo, useState } from "react"

import { Divider, Button, Checkbox, FormControlLabel, FormGroup } from "@mui/material"
import { Divider, Button, Checkbox, FormControlLabel, FormGroup, Tooltip } from "@mui/material"
import AutoRefreshToggle from "components/AutoRefreshToggle"
import RefreshButton from "components/RefreshButton"
import ColumnSelect from "components/lookoutV2/ColumnSelect"
import GroupBySelect from "components/lookoutV2/GroupBySelect"
Expand All @@ -25,6 +26,8 @@ export interface JobsTableActionBarProps {
activeJobSets: boolean
onActiveJobSetsChanged: (newVal: boolean) => void
onRefresh: () => void
autoRefresh: boolean
onAutoRefreshChange: (autoRefresh: boolean) => void
onAddAnnotationColumn: (annotationKey: string) => void
onRemoveAnnotationColumn: (colId: ColumnId) => void
onEditAnnotationColumn: (colId: ColumnId, annotationKey: string) => void
Expand All @@ -49,6 +52,8 @@ export const JobsTableActionBar = memo(
activeJobSets,
onActiveJobSetsChanged,
onRefresh,
autoRefresh,
onAutoRefreshChange,
onAddAnnotationColumn,
onRemoveAnnotationColumn,
onEditAnnotationColumn,
Expand Down Expand Up @@ -95,23 +100,27 @@ export const JobsTableActionBar = memo(

<div className={styles.actionGroup}>
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={activeJobSets}
onChange={(e) => {
onActiveJobSetsChanged(e.target.checked)
}}
/>
}
label="Active Job Sets"
/>
<Tooltip title="Only display job sets with at least one active job.">
<FormControlLabel
control={
<Checkbox
checked={activeJobSets}
onChange={(e) => {
onActiveJobSetsChanged(e.target.checked)
}}
/>
}
label="Active job sets only"
/>
</Tooltip>
</FormGroup>
<Divider orientation="vertical" />
<AutoRefreshToggle autoRefresh={autoRefresh} onAutoRefreshChange={onAutoRefreshChange} />
<RefreshButton isLoading={isLoading} onClick={onRefresh} />
<Divider orientation="vertical" />
<Button variant="text" onClick={onClearFilters} color="secondary">
Clear Filters
</Button>
<RefreshButton isLoading={isLoading} onClick={onRefresh} />
<CustomViewPicker
customViews={customViews}
onAddCustomView={onAddCustomView}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ describe("JobsTableContainer", () => {
logService={logService}
cordonService={new FakeCordonService()}
debug={false}
autoRefreshMs={30000}
/>
</SnackbarProvider>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import _ from "lodash"
import { isJobGroupRow, JobRow, JobTableRow } from "models/jobsTableModels"
import { Job, JobFilter, JobId, Match, SortDirection } from "models/lookoutV2Models"
import { useLocation, useNavigate, useParams } from "react-router-dom"
import IntervalService from "services/IntervalService"
import { IGetJobsService } from "services/lookoutV2/GetJobsService"
import { IGetRunErrorService } from "services/lookoutV2/GetRunErrorService"
import { IGroupJobsService } from "services/lookoutV2/GroupJobsService"
Expand Down Expand Up @@ -87,6 +88,7 @@ interface JobsTableContainerProps {
logService: ILogService
cordonService: ICordonService
debug: boolean
autoRefreshMs: number
}

export type LookoutColumnFilter = {
Expand Down Expand Up @@ -130,6 +132,7 @@ export const JobsTableContainer = ({
logService,
cordonService,
debug,
autoRefreshMs,
}: JobsTableContainerProps) => {
const openSnackbar = useCustomSnackbar()

Expand Down Expand Up @@ -176,13 +179,30 @@ export const JobsTableContainer = ({
})
const { pageIndex, pageSize } = useMemo(() => pagination, [pagination])

const [autoRefresh, setAutoRefresh] = useState<boolean>(
initialPrefs.autoRefresh === undefined ? true : initialPrefs.autoRefresh,
)

const autoRefreshService = useMemo(() => new IntervalService(autoRefreshMs), [autoRefreshMs])

const onAutoRefreshChange = (autoRefresh: boolean) => {
setAutoRefresh(autoRefresh)
if (autoRefresh) {
autoRefreshService.start()
} else {
autoRefreshService.stop()
}
}

// Filtering
const [columnFilterState, setColumnFilterState] = useState<ColumnFiltersState>(initialPrefs.filters)
const [lookoutFilters, setLookoutFilters] = useState<LookoutColumnFilter[]>([]) // Parsed later
const [columnMatches, setColumnMatches] = useState<Record<string, Match>>(initialPrefs.columnMatches)
const [parseErrors, setParseErrors] = useState<Record<string, string | undefined>>({})
const [textFieldRefs, setTextFieldRefs] = useState<Record<string, RefObject<HTMLInputElement>>>({})
const [activeJobSets, setActiveJobSets] = useState<boolean>(false)
const [activeJobSets, setActiveJobSets] = useState<boolean>(
initialPrefs.activeJobSets === undefined ? false : initialPrefs.activeJobSets,
)

// Sorting
const [lookoutOrder, setLookoutOrder] = useState<LookoutColumnOrder>(initialPrefs.order)
Expand Down Expand Up @@ -248,6 +268,8 @@ export const JobsTableContainer = ({
visibleColumns: columnVisibility,
sidebarJobId: sidebarJobId,
sidebarWidth: sidebarWidth,
activeJobSets: activeJobSets,
autoRefresh: autoRefresh,
}
}

Expand Down Expand Up @@ -284,6 +306,12 @@ export const JobsTableContainer = ({
setSidebarJobId(prefs.sidebarJobId)
setSidebarWidth(prefs.sidebarWidth ?? 600)
setSelectedRows({})
if (prefs.activeJobSets !== undefined) {
setActiveJobSets(prefs.activeJobSets)
}
if (prefs.autoRefresh !== undefined) {
onAutoRefreshChange(prefs.autoRefresh)
}

// Have to manually set text fields to the filter values since they are uncontrolled
setTextFields(prefs.filters)
Expand Down Expand Up @@ -311,6 +339,8 @@ export const JobsTableContainer = ({
selectedRows,
sidebarJobId,
sidebarWidth,
activeJobSets,
autoRefresh,
])

const addCustomView = (name: string) => {
Expand Down Expand Up @@ -339,6 +369,14 @@ export const JobsTableContainer = ({
setRowsToFetch(pendingDataForAllVisibleData(expanded, data, pageSize, pageIndex * pageSize))
}

useEffect(() => {
autoRefreshService.registerCallback(onRefresh)
if (autoRefresh) {
autoRefreshService.start()
}
return () => autoRefreshService.stop()
}, [])

const onColumnVisibilityChange = (colIdToToggle: ColumnId) => {
// Refresh if we make a new aggregate column visible
let shouldRefresh = false
Expand Down Expand Up @@ -703,6 +741,8 @@ export const JobsTableContainer = ({
onRefresh()
}}
onRefresh={onRefresh}
autoRefresh={autoRefresh}
onAutoRefreshChange={onAutoRefreshChange}
onAddAnnotationColumn={addAnnotationCol}
onRemoveAnnotationColumn={removeAnnotationCol}
onEditAnnotationColumn={editAnnotationCol}
Expand Down
Loading

0 comments on commit 5b5fd9e

Please sign in to comment.