Skip to content

Commit

Permalink
fix: improve alizer performances (redhat-developer#118)
Browse files Browse the repository at this point in the history
Signed-off-by: Luca Stocchi <lstocchi@redhat.com>
  • Loading branch information
lstocchi committed Dec 19, 2022
1 parent d66a110 commit 4930f12
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 60 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ Please note that the devfile object that is returned by the method is one of the

Alizer is also able to detect components. The concept of component is taken from Odo and its definition can be read on [odo.dev](https://odo.dev/docs/getting-started/basics/#component).

The detection of a component is based on two rules. It is discovered if and only if:

1) The main language of the component source is one of those that supports component detection (Java, Python, Javascript, Go)
2) The source has at least one framework
The detection of a component is based on only one rule. It is discovered if and only if the main language of the component source is one of those that supports component detection (Java, Python, Javascript, Go, ...)

The result is a list of components where each component consists of:
- *path*: root of the component
Expand Down
2 changes: 1 addition & 1 deletion go/pkg/apis/enricher/enricher.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func GetPortsFromDockerComposeFile(componentPath string, settings model.Detectio
}

func getDockerComposeFileBytes(root string) ([]byte, error) {
return utils.ReadAnyApplicationFile(root, []model.ApplicationFileInfo{
return utils.ReadAnyApplicationFileExactMatch(root, []model.ApplicationFileInfo{
{
Dir: "",
File: "docker-compose.yml",
Expand Down
2 changes: 1 addition & 1 deletion go/pkg/apis/enricher/framework/go/echo_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (e EchoDetector) DoFrameworkDetection(language *model.Language, goMod *modf
}

func (e EchoDetector) DoPortsDetection(component *model.Component) {
files, err := utils.GetFilePathsFromRoot(component.Path)
files, err := utils.GetCachedFilePathsFromRoot(component.Path)
if err != nil {
return
}
Expand Down
2 changes: 1 addition & 1 deletion go/pkg/apis/enricher/framework/go/fasthttp_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (f FastHttpDetector) DoFrameworkDetection(language *model.Language, goMod *
}

func (f FastHttpDetector) DoPortsDetection(component *model.Component) {
files, err := utils.GetFilePathsFromRoot(component.Path)
files, err := utils.GetCachedFilePathsFromRoot(component.Path)
if err != nil {
return
}
Expand Down
2 changes: 1 addition & 1 deletion go/pkg/apis/enricher/framework/go/gin_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (g GinDetector) DoFrameworkDetection(language *model.Language, goMod *modfi
}

func (g GinDetector) DoPortsDetection(component *model.Component) {
files, err := utils.GetFilePathsFromRoot(component.Path)
files, err := utils.GetCachedFilePathsFromRoot(component.Path)
if err != nil {
return
}
Expand Down
2 changes: 1 addition & 1 deletion go/pkg/apis/enricher/framework/go/gofiber_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (g GoFiberDetector) DoFrameworkDetection(language *model.Language, goMod *m
}

func (g GoFiberDetector) DoPortsDetection(component *model.Component) {
files, err := utils.GetFilePathsFromRoot(component.Path)
files, err := utils.GetCachedFilePathsFromRoot(component.Path)
if err != nil {
return
}
Expand Down
2 changes: 1 addition & 1 deletion go/pkg/apis/enricher/framework/go/mux_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (m MuxDetector) DoFrameworkDetection(language *model.Language, goMod *modfi
}

func (m MuxDetector) DoPortsDetection(component *model.Component) {
files, err := utils.GetFilePathsFromRoot(component.Path)
files, err := utils.GetCachedFilePathsFromRoot(component.Path)
if err != nil {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (e ExpressDetector) DoFrameworkDetection(language *model.Language, config s
}

func (e ExpressDetector) DoPortsDetection(component *model.Component) {
files, err := utils.GetFilePathsFromRoot(component.Path)
files, err := utils.GetCachedFilePathsFromRoot(component.Path)
if err != nil {
return
}
Expand Down
116 changes: 78 additions & 38 deletions go/pkg/apis/recognizer/component_recognizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ package recognizer
import (
"errors"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -46,32 +47,21 @@ func DetectComponentsWithPathAndPortStartegy(path string, portDetectionStrategy
}

func DetectComponentsInRootWithSettings(settings model.DetectionSettings) ([]model.Component, error) {
files, err := utils.GetFilePathsFromRoot(settings.BasePath)
files, err := utils.GetFilePathsInRoot(settings.BasePath)
if err != nil {
return []model.Component{}, err
}
components, err := DetectComponentsFromFilesList(files, settings)
if err != nil {
return []model.Component{}, err
}

// it may happen that a language has no a specific configuration file (e.g opposite to JAVA -> pom.xml and Nodejs -> package.json)
// we then rely on the language recognizer
directoriesNotBelongingToExistingComponent := getDirectoriesWithoutConfigFile(settings.BasePath, components)
components = append(components, getComponentsWithoutConfigFile(directoriesNotBelongingToExistingComponent, settings)...)
components := DetectComponentsFromFilesList(files, settings)

return components, nil
}

func DetectComponentsWithSettings(settings model.DetectionSettings) ([]model.Component, error) {
files, err := utils.GetFilePathsFromRoot(settings.BasePath)
if err != nil {
return []model.Component{}, err
}
components, err := DetectComponentsFromFilesList(files, settings)
files, err := utils.GetCachedFilePathsFromRoot(settings.BasePath)
if err != nil {
return []model.Component{}, err
}
components := DetectComponentsFromFilesList(files, settings)

// it may happen that a language has no a specific configuration file (e.g opposite to JAVA -> pom.xml and Nodejs -> package.json)
// we then rely on the language recognizer
Expand All @@ -92,8 +82,8 @@ func DetectComponentsWithSettings(settings model.DetectionSettings) ([]model.Com
func getComponentsWithoutConfigFile(directories []string, settings model.DetectionSettings) []model.Component {
var components []model.Component
for _, dir := range directories {
component, _ := detectComponent(dir, []string{}, settings)
if component.Path != "" && isLangForNoConfigComponent(component.Languages) {
component, _ := detectComponentByFolderAnalysis(dir, []string{}, settings)
if component.Path != "" && isLangForNoConfigComponent(component.Languages[0]) {
components = append(components, component)
}
}
Expand All @@ -107,12 +97,8 @@ func getComponentsWithoutConfigFile(directories []string, settings model.Detecti
Returns:
bool: true if language does not require any config file
*/
func isLangForNoConfigComponent(languages []model.Language) bool {
if len(languages) == 0 {
return false
}

lang, err := langfiles.Get().GetLanguageByNameOrAlias(languages[0].Name)
func isLangForNoConfigComponent(language model.Language) bool {
lang, err := langfiles.Get().GetLanguageByNameOrAlias(language.Name)
if err != nil {
return false
}
Expand Down Expand Up @@ -209,29 +195,50 @@ func isFirstPathParentOfSecond(firstPath string, secondPath string) bool {
Returns:
list of components detected or err if any error occurs
*/
func DetectComponentsFromFilesList(files []string, settings model.DetectionSettings) ([]model.Component, error) {
func DetectComponentsFromFilesList(files []string, settings model.DetectionSettings) []model.Component {
configurationPerLanguage := langfiles.Get().GetConfigurationPerLanguageMapping()
var components []model.Component
for _, file := range files {
dir, fileName := filepath.Split(file)
if dir == "" {
dir = "./"
languages, err := getLanguagesByConfigurationFile(configurationPerLanguage, file)
if err != nil {
continue
}
languages, err := getLanguagesByConfigurationFile(configurationPerLanguage, fileName)
// check if possible to pick only one lang (e.g tsconfig.json or jsconfig.json)
languages = narrowLanguagesList(languages, file)

component, err := detectComponentUsingConfigFile(file, languages, settings)
if err != nil {
continue
}
for _, language := range languages {
if isConfigurationValid(language, file) {
component, _ := detectComponent(dir, languages, settings)
if component.Path != "" {
components = appendIfMissing(components, component)
break
}
components = appendIfMissing(components, component)
}
return components
}

// todo maybe it's better to remove
func narrowLanguagesList(languages []string, file string) []string {
if len(languages) == 1 {
return languages
}

dir, _ := utils.NormalizeSplit(file)
narrowedLanguages := []string{}
for _, language := range languages {
if strings.EqualFold(language, "javascript") {
if _, err := os.Stat(filepath.Join(dir, "jsconfig.json")); !os.IsNotExist(err) {
narrowedLanguages = append(narrowedLanguages, language)
}
} else if strings.EqualFold(language, "typescript") {
if _, err := os.Stat(filepath.Join(dir, "tsconfig.json")); !os.IsNotExist(err) {
narrowedLanguages = append(narrowedLanguages, language)
}
}
}
return components, nil

if len(narrowedLanguages) == 0 {
return languages
}
return narrowedLanguages
}

func appendIfMissing(components []model.Component, component model.Component) []model.Component {
Expand Down Expand Up @@ -263,7 +270,7 @@ func getLanguagesByConfigurationFile(configurationPerLanguage map[string][]strin
Returns:
component detected or error if any error occurs
*/
func detectComponent(root string, configLanguages []string, settings model.DetectionSettings) (model.Component, error) {
func detectComponentByFolderAnalysis(root string, configLanguages []string, settings model.DetectionSettings) (model.Component, error) {
languages, err := Analyze(root)
if err != nil {
return model.Component{}, err
Expand All @@ -280,8 +287,41 @@ func detectComponent(root string, configLanguages []string, settings model.Detec
}
}

return model.Component{}, nil
return model.Component{}, errors.New("no component detected")

}

func detectComponentByAnalyzingConfigFile(file string, mainLang string, settings model.DetectionSettings) (model.Component, error) {
if !isConfigurationValid(mainLang, file) {
return model.Component{}, errors.New("language not valid for component detection")
}
dir, _ := utils.NormalizeSplit(file)
lang, err := AnalyzeFile(file, mainLang)
if err != nil {
return model.Component{}, err
}
component := model.Component{
Path: dir,
Languages: []model.Language{
lang,
},
}
enrichComponent(&component, settings)
return component, nil
}

func detectComponentUsingConfigFile(file string, languages []string, settings model.DetectionSettings) (model.Component, error) {
if len(languages) == 1 {
return detectComponentByAnalyzingConfigFile(file, languages[0], settings)
} else {
dir, _ := utils.NormalizeSplit(file)
for _, language := range languages {
if isConfigurationValid(language, file) {
return detectComponentByFolderAnalysis(dir, languages, settings)
}
}
}
return model.Component{}, errors.New("no component detected")
}

func enrichComponent(component *model.Component, settings model.DetectionSettings) {
Expand Down
21 changes: 20 additions & 1 deletion go/pkg/apis/recognizer/language_recognizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func Analyze(path string) ([]model.Language, error) {
languagesFile := langfile.Get()
languagesDetected := make(map[string]languageItem)

paths, err := utils.GetFilePathsFromRoot(path)
paths, err := utils.GetCachedFilePathsFromRoot(path)
if err != nil {
return []model.Language{}, err
}
Expand Down Expand Up @@ -93,6 +93,25 @@ func Analyze(path string) ([]model.Language, error) {
return languagesFound, nil
}

func AnalyzeFile(configFile string, targetLanguage string) (model.Language, error) {
lang, err := langfile.Get().GetLanguageByName(targetLanguage)
if err != nil {
return model.Language{}, err
}
tmpLanguage := model.Language{
Name: lang.Name,
Aliases: lang.Aliases,
Frameworks: []string{},
Tools: []string{},
Weight: 100,
CanBeComponent: true}
langEnricher := enricher.GetEnricherByLanguage(targetLanguage)
if langEnricher != nil {
langEnricher.DoEnrichLanguage(&tmpLanguage, &[]string{configFile})
}
return tmpLanguage, nil
}

func extractExtensions(paths []string) map[string]int {
extensions := make(map[string]int)
for _, path := range paths {
Expand Down
16 changes: 16 additions & 0 deletions go/pkg/utils/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package utils

var filePathsFromRoot = make(map[string][]string)

func GetCachedFilePathsFromRoot(root string) ([]string, error) {
if files, hasRoot := filePathsFromRoot[root]; hasRoot {
return files, nil
}

filePaths, err := GetFilePathsFromRoot(root)
if err != nil {
return []string{}, err
}
filePathsFromRoot[root] = filePaths
return filePaths, nil
}
36 changes: 34 additions & 2 deletions go/pkg/utils/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func IsValidPort(port int) bool {
}

func GetAnyApplicationFilePath(root string, propsFiles []model.ApplicationFileInfo) string {
files, err := GetFilePathsFromRoot(root)
files, err := GetCachedFilePathsFromRoot(root)
if err != nil {
return ""
}
Expand All @@ -292,8 +292,32 @@ func GetAnyApplicationFilePath(root string, propsFiles []model.ApplicationFileIn
return ""
}

func GetAnyApplicationFilePathExactMatch(root string, propsFiles []model.ApplicationFileInfo) string {
for _, propsFile := range propsFiles {
fileToBeFound := filepath.Join(root, propsFile.Dir, propsFile.File)
if _, err := os.Stat(fileToBeFound); !os.IsNotExist(err) {
return fileToBeFound
}
}

return ""
}

func ReadAnyApplicationFile(root string, propsFiles []model.ApplicationFileInfo) ([]byte, error) {
path := GetAnyApplicationFilePath(root, propsFiles)
return readAnyApplicationFile(root, propsFiles, false)
}

func ReadAnyApplicationFileExactMatch(root string, propsFiles []model.ApplicationFileInfo) ([]byte, error) {
return readAnyApplicationFile(root, propsFiles, true)
}

func readAnyApplicationFile(root string, propsFiles []model.ApplicationFileInfo, exactMatch bool) ([]byte, error) {
var path string
if exactMatch {
path = GetAnyApplicationFilePathExactMatch(root, propsFiles)
} else {
path = GetAnyApplicationFilePath(root, propsFiles)
}
if path != "" {
return ioutil.ReadFile(path)
}
Expand Down Expand Up @@ -375,3 +399,11 @@ func getEnvFileContent(root string) (string, error) {
}
return string(bytes), nil
}

func NormalizeSplit(file string) (string, string) {
dir, fileName := filepath.Split(file)
if dir == "" {
dir = "./"
}
return dir, fileName
}
Loading

0 comments on commit 4930f12

Please sign in to comment.