Skip to content
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,99 @@ flowchart TD

The plugin architecture allows custom scheduler implementations to override default behavior while maintaining compatibility with the core Gthulhu scheduler framework.

## Factory Pattern

This repository implements a factory pattern for plugin creation, allowing dynamic plugin registration and instantiation without hardcoded switch-case logic in the Gthulhu main repository.

### Quick Start

```go
import (
"github.com/Gthulhu/plugin/plugin"
// Import plugins to trigger registration
_ "github.com/Gthulhu/plugin/plugin/gthulhu"
_ "github.com/Gthulhu/plugin/plugin/simple"
)

// Create a scheduler plugin using the factory
config := &plugin.SchedConfig{
Mode: "gthulhu",
SliceNsDefault: 5000 * 1000, // 5ms
SliceNsMin: 500 * 1000, // 0.5ms
}

scheduler, err := plugin.NewSchedulerPlugin(config)
if err != nil {
log.Fatalf("Failed to create plugin: %v", err)
}

// Use the scheduler
scheduler.DrainQueuedTask(sched)
```

### Available Plugins

| Mode | Description |
|------|-------------|
| `gthulhu` | Advanced scheduler with API integration and scheduling strategies |
| `simple` | Simple weighted vtime scheduler |
| `simple-fifo` | Simple FIFO scheduler |

### Configuration

The `SchedConfig` struct holds all configuration parameters:

```go
type SchedConfig struct {
Mode string // Plugin mode to use
SliceNsDefault uint64 // Default time slice in nanoseconds
SliceNsMin uint64 // Minimum time slice in nanoseconds
FifoMode bool // Enable FIFO mode for simple plugin

// Scheduler configuration (for Gthulhu plugin)
Scheduler struct {
SliceNsDefault uint64
SliceNsMin uint64
}

// API configuration (for Gthulhu plugin)
APIConfig struct {
PublicKeyPath string
BaseURL string
}
}
```

### Creating New Plugins

To add a new plugin:

1. Implement the `CustomScheduler` interface
2. Register your plugin in `init()`:

```go
func init() {
plugin.RegisterNewPlugin("myplugin", func(config *plugin.SchedConfig) (plugin.CustomScheduler, error) {
return NewMyPlugin(config), nil
})
}
```

3. Import your plugin package to trigger registration

## Testing

Run tests with coverage:

```bash
# Run all tests
go test ./... -v

# Check coverage
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out

# View coverage in browser
go tool cover -html=coverage.out
```

42 changes: 42 additions & 0 deletions examples/usage_example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"fmt"
"log"

"github.com/Gthulhu/plugin/plugin"
// Import plugin packages to trigger init() registration
_ "github.com/Gthulhu/plugin/plugin/gthulhu"
_ "github.com/Gthulhu/plugin/plugin/simple"
)

// Example showing how to use this in Gthulhu main.go
func gthulhuMainExample() {
// Create plugin configuration
pluginConfig := &plugin.SchedConfig{
Mode: "gthulhu",
SliceNsDefault: 5000 * 1000,
SliceNsMin: 500 * 1000,
}
pluginConfig.Scheduler.SliceNsDefault = 5000 * 1000
pluginConfig.Scheduler.SliceNsMin = 500 * 1000

// Create the plugin using the factory
scheduler, err := plugin.NewSchedulerPlugin(pluginConfig)
if err != nil {
log.Fatalf("Failed to create scheduler plugin: %v", err)
}

fmt.Printf("Created plugin successfully, pool count: %d\n", scheduler.GetPoolCount())
}

func main() {
fmt.Println("=== Gthulhu Plugin Factory Example ===")

// List available plugins
modes := plugin.GetRegisteredModes()
fmt.Printf("Available plugin modes: %v\n\n", modes)

// Create plugin instance
gthulhuMainExample()
}
37 changes: 37 additions & 0 deletions plugin/gthulhu/gthulhu.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@ import (
"github.com/Gthulhu/plugin/plugin"
)

func init() {
// Register the gthulhu plugin with the factory
err := plugin.RegisterNewPlugin("gthulhu", func(config *plugin.SchedConfig) (plugin.CustomScheduler, error) {
// Use Scheduler config if available, otherwise use SimpleScheduler config
sliceNsDefault := config.Scheduler.SliceNsDefault
sliceNsMin := config.Scheduler.SliceNsMin

if sliceNsDefault == 0 && config.SliceNsDefault > 0 {
sliceNsDefault = config.SliceNsDefault
}
if sliceNsMin == 0 && config.SliceNsMin > 0 {
sliceNsMin = config.SliceNsMin
}

gthulhuPlugin := NewGthulhuPlugin(sliceNsDefault, sliceNsMin)

// Initialize JWT client if API config is provided
if config.APIConfig.PublicKeyPath != "" && config.APIConfig.BaseURL != "" {
err := gthulhuPlugin.InitJWTClient(config.APIConfig.PublicKeyPath, config.APIConfig.BaseURL)
if err != nil {
return nil, err
}

// Initialize metrics client
err = gthulhuPlugin.InitMetricsClient(config.APIConfig.BaseURL)
if err != nil {
return nil, err
}
}

return gthulhuPlugin, nil
})
if err != nil {
panic(err)
}
}

type GthulhuPlugin struct {
// Scheduler configuration
sliceNsDefault uint64
Expand Down
92 changes: 91 additions & 1 deletion plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package plugin

import "github.com/Gthulhu/plugin/models"
import (
"fmt"
"sync"

"github.com/Gthulhu/plugin/models"
)

type Sched interface {
DequeueTask(task *models.QueuedTask)
Expand All @@ -20,3 +25,88 @@ type CustomScheduler interface {
// GetPoolCount will be called by the scheduler to notify the number of tasks waiting to be dispatched (NotifyComplete)
GetPoolCount() uint64
}

// SchedConfig holds the configuration parameters for creating a scheduler plugin
type SchedConfig struct {
// Mode specifies which scheduler plugin to use (e.g., "gthulhu", "simple", "simple-fifo")
Mode string `yaml:"mode"`

// SimpleScheduler configuration
SliceNsDefault uint64 `yaml:"slice_ns_default"`
SliceNsMin uint64 `yaml:"slice_ns_min"`
FifoMode bool `yaml:"fifo_mode"`

// Scheduler configuration (for Gthulhu plugin)
// These match the parameters that would be passed from the Gthulhu main repo
Scheduler struct {
SliceNsDefault uint64 `yaml:"slice_ns_default"`
SliceNsMin uint64 `yaml:"slice_ns_min"`
} `yaml:"scheduler"`

// API configuration
APIConfig struct {
PublicKeyPath string `yaml:"public_key_path"`
BaseURL string `yaml:"base_url"`
} `yaml:"api_config"`
}

// PluginFactory is a function type that creates a CustomScheduler instance
type PluginFactory func(config *SchedConfig) (CustomScheduler, error)

var (
// pluginRegistry stores registered plugin factories
pluginRegistry = make(map[string]PluginFactory)
registryMutex sync.RWMutex
)

// RegisterNewPlugin registers a plugin factory for a specific mode
// This should be called in the init() function of each plugin implementation
func RegisterNewPlugin(mode string, factory PluginFactory) error {
registryMutex.Lock()
defer registryMutex.Unlock()

if mode == "" {
return fmt.Errorf("plugin mode cannot be empty")
}

if factory == nil {
return fmt.Errorf("plugin factory cannot be nil")
}

if _, exists := pluginRegistry[mode]; exists {
return fmt.Errorf("plugin mode '%s' is already registered", mode)
}

pluginRegistry[mode] = factory
return nil
}

// NewSchedulerPlugin creates a new scheduler plugin based on the configuration
// This is the factory function that follows the simple factory pattern
func NewSchedulerPlugin(config *SchedConfig) (CustomScheduler, error) {
if config == nil {
return nil, fmt.Errorf("config cannot be nil")
}

registryMutex.RLock()
factory, exists := pluginRegistry[config.Mode]
registryMutex.RUnlock()

if !exists {
return nil, fmt.Errorf("unknown plugin mode: %s", config.Mode)
}

return factory(config)
}

// GetRegisteredModes returns a list of all registered plugin modes
func GetRegisteredModes() []string {
registryMutex.RLock()
defer registryMutex.RUnlock()

modes := make([]string, 0, len(pluginRegistry))
for mode := range pluginRegistry {
modes = append(modes, mode)
}
return modes
}
Loading
Loading