Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials

7019 Articles
article-image-introduction-custom-template-filters-and-tags
Packt
13 Oct 2014
25 min read
Save for later

Introduction to Custom Template Filters and Tags

Packt
13 Oct 2014
25 min read
This article is written by Aidas Bendoratis, the author of Web Development with Django Cookbook. In this article, we will cover the following recipes: Following conventions for your own template filters and tags Creating a template filter to show how many days have passed Creating a template filter to extract the first media object Creating a template filter to humanize URLs Creating a template tag to include a template if it exists Creating a template tag to load a QuerySet in a template Creating a template tag to parse content as a template Creating a template tag to modify request query parameters As you know, Django has quite an extensive template system, with features such as template inheritance, filters for changing the representation of values, and tags for presentational logic. Moreover, Django allows you to add your own template filters and tags in your apps. Custom filters or tags should be located in a template-tag library file under the templatetags Python package in your app. Your template-tag library can then be loaded in any template with a {% load %} template tag. In this article, we will create several useful filters and tags that give more control to the template editors. Following conventions for your own template filters and tags Custom template filters and tags can become a total mess if you don't have persistent guidelines to follow. Template filters and tags should serve template editors as much as possible. They should be both handy and flexible. In this recipe, we will look at some conventions that should be used when enhancing the functionality of the Django template system. How to do it... Follow these conventions when extending the Django template system: Don't create or use custom template filters or tags when the logic for the page fits better in the view, context processors, or in model methods. When your page is context-specific, such as a list of objects or an object-detail view, load the object in the view. If you need to show some content on every page, create a context processor. Use custom methods of the model instead of template filters when you need to get some properties of an object not related to the context of the template. Name the template-tag library with the _tags suffix. When your app is named differently than your template-tag library, you can avoid ambiguous package importing problems. In the newly created library, separate filters from tags, for example, by using comments such as the following: # -*- coding: UTF-8 -*-from django import templateregister = template.Library()### FILTERS #### .. your filters go here..### TAGS #### .. your tags go here.. Create template tags that are easy to remember by including the following constructs: for [app_name.model_name]: Include this construct to use a specific model using [template_name]: Include this construct to use a template for the output of the template tag limit [count]: Include this construct to limit the results to a specific amount as [context_variable]: Include this construct to save the results to a context variable that can be reused many times later Try to avoid multiple values defined positionally in template tags unless they are self-explanatory. Otherwise, this will likely confuse the template developers. Make as many arguments resolvable as possible. Strings without quotes should be treated as context variables that need to be resolved or as short words that remind you of the structure of the template tag components. See also The Creating a template filter to show how many days have passed recipe The Creating a template filter to extract the first media object recipe The Creating a template filter to humanize URLs recipe The Creating a template tag to include a template if it exists recipe The Creating a template tag to load a QuerySet in a template recipe The Creating a template tag to parse content as a template recipe The Creating a template tag to modify request query parameters recipe Creating a template filter to show how many days have passed Not all people keep track of the date, and when talking about creation or modification dates of cutting-edge information, for many of us, it is more convenient to read the time difference, for example, the blog entry was posted three days ago, the news article was published today, and the user last logged in yesterday. In this recipe, we will create a template filter named days_since that converts dates to humanized time differences. Getting ready Create the utils app and put it under INSTALLED_APPS in the settings, if you haven't done that yet. Then, create a Python package named templatetags inside this app (Python packages are directories with an empty __init__.py file). How to do it... Create a utility_tags.py file with this content: #utils/templatetags/utility_tags.py# -*- coding: UTF-8 -*-from datetime import datetimefrom django import templatefrom django.utils.translation import ugettext_lazy as _from django.utils.timezone import now as tz_nowregister = template.Library()### FILTERS ###@register.filterdef days_since(value):""" Returns number of days between today and value."""today = tz_now().date()if isinstance(value, datetime.datetime):value = value.date()diff = today - valueif diff.days > 1:return _("%s days ago") % diff.dayselif diff.days == 1:return _("yesterday")elif diff.days == 0:return _("today")else:# Date is in the future; return formatted date.return value.strftime("%B %d, %Y") How it works... If you use this filter in a template like the following, it will render something like yesterday or 5 days ago: {% load utility_tags %}{{ object.created|days_since }} You can apply this filter to the values of the date and datetime types. Each template-tag library has a register where filters and tags are collected. Django filters are functions registered by the register.filter decorator. By default, the filter in the template system will be named the same as the function or the other callable object. If you want, you can set a different name for the filter by passing name to the decorator, as follows: @register.filter(name="humanized_days_since")def days_since(value):... The filter itself is quite self-explanatory. At first, the current date is read. If the given value of the filter is of the datetime type, the date is extracted. Then, the difference between today and the extracted value is calculated. Depending on the number of days, different string results are returned. There's more... This filter is easy to extend to also show the difference in time, such as just now, 7 minutes ago, or 3 hours ago. Just operate the datetime values instead of the date values. See also The Creating a template filter to extract the first media object recipe The Creating a template filter to humanize URLs recipe Creating a template filter to extract the first media object Imagine that you are developing a blog overview page, and for each post, you want to show images, music, or videos in that page taken from the content. In such a case, you need to extract the <img>, <object>, and <embed> tags out of the HTML content of the post. In this recipe, we will see how to do this using regular expressions in the get_first_media filter. Getting ready We will start with the utils app that should be set in INSTALLED_APPS in the settings and the templatetags package inside this app. How to do it... In the utility_tags.py file, add the following content: #utils/templatetags/utility_tags.py# -*- coding: UTF-8 -*-import refrom django import templatefrom django.utils.safestring import mark_saferegister = template.Library()### FILTERS ###media_file_regex = re.compile(r"<object .+?</object>|"r"<(img|embed) [^>]+>") )@register.filterdef get_first_media(content):""" Returns the first image or flash file from the htmlcontent """m = media_file_regex.search(content)media_tag = ""if m:media_tag = m.group()return mark_safe(media_tag) How it works... While the HTML content in the database is valid, when you put the following code in the template, it will retrieve the <object>, <img>, or <embed> tags from the content field of the object, or an empty string if no media is found there: {% load utility_tags %} {{ object.content|get_first_media }} At first, we define the compiled regular expression as media_file_regex, then in the filter, we perform a search for that regular expression pattern. By default, the result will show the <, >, and & symbols escaped as &lt;, &gt;, and &amp; entities. But we use the mark_safe function that marks the result as safe HTML ready to be shown in the template without escaping. There's more... It is very easy to extend this filter to also extract the <iframe> tags (which are more recently being used by Vimeo and YouTube for embedded videos) or the HTML5 <audio> and <video> tags. Just modify the regular expression like this: media_file_regex = re.compile(r"<iframe .+?</iframe>|"r"<audio .+?</ audio>|<video .+?</video>|"r"<object .+?</object>|<(img|embed) [^>]+>") See also The Creating a template filter to show how many days have passed recipe The Creating a template filter to humanize URLs recipe Creating a template filter to humanize URLs Usually, common web users enter URLs into address fields without protocol and trailing slashes. In this recipe, we will create a humanize_url filter used to present URLs to the user in a shorter format, truncating very long addresses, just like what Twitter does with the links in tweets. Getting ready As in the previous recipes, we will start with the utils app that should be set in INSTALLED_APPS in the settings, and should contain the templatetags package. How to do it... In the FILTERS section of the utility_tags.py template library in the utils app, let's add a filter named humanize_url and register it: #utils/templatetags/utility_tags.py# -*- coding: UTF-8 -*-import refrom django import templateregister = template.Library()### FILTERS ###@register.filterdef humanize_url(url, letter_count):""" Returns a shortened human-readable URL """letter_count = int(letter_count)re_start = re.compile(r"^https?://")re_end = re.compile(r"/$")url = re_end.sub("", re_start.sub("", url))if len(url) > letter_count:url = u"%s…" % url[:letter_count - 1]return url How it works... We can use the humanize_url filter in any template like this: {% load utility_tags %}<a href="{{ object.website }}" target="_blank">{{ object.website|humanize_url:30 }}</a> The filter uses regular expressions to remove the leading protocol and the trailing slash, and then shortens the URL to the given amount of letters, adding an ellipsis to the end if the URL doesn't fit into the specified letter count. See also The Creating a template filter to show how many days have passed recipe The Creating a template filter to extract the first media object recipe The Creating a template tag to include a template if it exists recipe Creating a template tag to include a template if it exists Django has the {% include %} template tag that renders and includes another template. However, in some particular situations, there is a problem that an error is raised if the template does not exist. In this recipe, we will show you how to create a {% try_to_include %} template tag that includes another template, but fails silently if there is no such template. Getting ready We will start again with the utils app that should be installed and is ready for custom template tags. How to do it... Template tags consist of two things: the function parsing the arguments of the template tag and the node class that is responsible for the logic of the template tag as well as for the output. Perform the following steps: First, let's create the function parsing the template-tag arguments: #utils/templatetags/utility_tags.py# -*- coding: UTF-8 -*-from django import templatefrom django.template.loader import get_templateregister = template.Library()### TAGS ###@register.tagdef try_to_include(parser, token):"""Usage: {% try_to_include "sometemplate.html" %}This will fail silently if the template doesn't exist.If it does, it will be rendered with the current context."""try:tag_name, template_name = token.split_contents()except ValueError:raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents.split()[0]return IncludeNode(template_name) Then, we need the node class in the same file, as follows: class IncludeNode(template.Node):def __init__(self, template_name):self.template_name = template_namedef render(self, context):try:# Loading the template and rendering ittemplate_name = template.resolve_variable(self. template_name, context)included_template = get_template(template_name).render(context)except template.TemplateDoesNotExist:included_template = ""return included_template How it works... The {% try_to_include %} template tag expects one argument, that is, template_name. So, in the try_to_include function, we are trying to assign the split contents of the token only to the tag_name variable (which is "try_to_include") and the template_name variable. If this doesn't work, the template syntax error is raised. The function returns the IncludeNode object, which gets the template_name field for later usage. In the render method of IncludeNode, we resolve the template_name variable. If a context variable was passed to the template tag, then its value will be used here for template_name. If a quoted string was passed to the template tag, then the content within quotes will be used for template_name. Lastly, we try to load the template and render it with the current template context. If that doesn't work, an empty string is returned. There are at least two situations where we could use this template tag: When including a template whose path is defined in a model, as follows: {% load utility_tags %}{% try_to_include object.template_path %} When including a template whose path is defined with the {% with %} template tag somewhere high in the template context variable's scope. This is especially useful when you need to create custom layouts for plugins in the placeholder of a template in Django CMS: #templates/cms/start_page.html{% with editorial_content_template_path="cms/plugins/editorial_content/start_page.html" %}{% placeholder "main_content" %}{% endwith %}#templates/cms/plugins/editorial_content.html{% load utility_tags %}{% if editorial_content_template_path %}{% try_to_include editorial_content_template_path %}{% else %}<div><!-- Some default presentation ofeditorial content plugin --></div>{% endif % There's more... You can use the {% try_to_include %} tag as well as the default {% include %} tag to include templates that extend other templates. This has a beneficial use for large-scale portals where you have different kinds of lists in which complex items share the same structure as widgets but have a different source of data. For example, in the artist list template, you can include the artist item template as follows: {% load utility_tags %}{% for object in object_list %}{% try_to_include "artists/includes/artist_item.html" %}{% endfor %} This template will extend from the item base as follows: {# templates/artists/includes/artist_item.html #}{% extends "utils/includes/item_base.html" %}  {% block item_title %}{{ object.first_name }} {{ object.last_name }}{% endblock %} The item base defines the markup for any item and also includes a Like widget, as follows: {# templates/utils/includes/item_base.html #}{% load likes_tags %}<h3>{% block item_title %}{% endblock %}</h3>{% if request.user.is_authenticated %}{% like_widget for object %}{% endif %} See also  The Creating a template tag to load a QuerySet in a template recipe The Creating a template tag to parse content as a template recipe The Creating a template tag to modify request query parameters recipe Creating a template tag to load a QuerySet in a template Most often, the content that should be shown in a web page will have to be defined in the view. If this is the content to show on every page, it is logical to create a context processor. Another situation is when you need to show additional content such as the latest news or a random quote on some specific pages, for example, the start page or the details page of an object. In this case, you can load the necessary content with the {% get_objects %} template tag, which we will implement in this recipe. Getting ready Once again, we will start with the utils app that should be installed and ready for custom template tags. How to do it... Template tags consist of function parsing arguments passed to the tag and a node class that renders the output of the tag or modifies the template context. Perform the following steps: First, let's create the function parsing the template-tag arguments, as follows: #utils/templatetags/utility_tags.py# -*- coding: UTF-8 -*-from django.db import modelsfrom django import templateregister = template.Library()### TAGS ###@register.tagdef get_objects(parser, token):"""Gets a queryset of objects of the model specified by appandmodel namesUsage:{% get_objects [<manager>.]<method> from<app_name>.<model_name> [limit <amount>] as<var_name> %}Example:{% get_objects latest_published from people.Personlimit 3 as people %}{% get_objects site_objects.all from news.Articlelimit 3 as articles %}{% get_objects site_objects.all from news.Articleas articles %}"""amount = Nonetry:tag_name, manager_method, str_from, appmodel,str_limit,amount, str_as, var_name = token.split_contents()except ValueError:try:tag_name, manager_method, str_from, appmodel, str_as,var_name = token.split_contents()except ValueError:raise template.TemplateSyntaxError, "get_objects tag requires a following syntax: ""{% get_objects [<manager>.]<method> from ""<app_ name>.<model_name>"" [limit <amount>] as <var_name> %}"try:app_name, model_name = appmodel.split(".")except ValueError:raise template.TemplateSyntaxError, "get_objects tag requires application name and ""model name separated by a dot"model = models.get_model(app_name, model_name)return ObjectsNode(model, manager_method, amount, var_name) Then, we create the node class in the same file, as follows: class ObjectsNode(template.Node):def __init__(self, model, manager_method, amount, var_name):self.model = modelself.manager_method = manager_methodself.amount = amountself.var_name = var_namedef render(self, context):if "." in self.manager_method:manager, method = self.manager_method.split(".")else:manager = "_default_manager"method = self.manager_methodqs = getattr(getattr(self.model, manager),method,self.model._default_manager.none,)()if self.amount:amount = template.resolve_variable(self.amount,context)context[self.var_name] = qs[:amount]else:context[self.var_name] = qsreturn "" How it works... The {% get_objects %} template tag loads a QuerySet defined by the manager method from a specified app and model, limits the result to the specified amount, and saves the result to a context variable. This is the simplest example of how to use the template tag that we have just created. It will load five news articles in any template using the following snippet: {% load utility_tags %}{% get_objects all from news.Article limit 5 as latest_articles %}{% for article in latest_articles %}<a href="{{ article.get_url_path }}">{{ article.title }}</a>{% endfor %} This is using the all method of the default objects manager of the Article model, and will sort the articles by the ordering attribute defined in the Meta class. A more advanced example would be required to create a custom manager with a custom method to query objects from the database. A manager is an interface that provides database query operations to models. Each model has at least one manager called objects by default. As an example, let's create the Artist model, which has a draft or published status, and a new manager, custom_manager, which allows you to select random published artists: #artists/models.py# -*- coding: UTF-8 -*-from django.db import modelsfrom django.utils.translation import ugettext_lazy as _STATUS_CHOICES = (('draft', _("Draft"),('published', _("Published"),)class ArtistManager(models.Manager):def random_published(self):return self.filter(status="published").order_by('?')class Artist(models.Model):# ...status = models.CharField(_("Status"), max_length=20,choices=STATUS_CHOICES)custom_manager = ArtistManager() To load a random published artist, you add the following snippet to any template: {% load utility_tags %}{% get_objects custom_manager.random_published from artists.Artistlimit 1 as random_artists %}{% for artist in random_artists %}{{ artist.first_name }} {{ artist.last_name }}{% endfor %} Let's look at the code of the template tag. In the parsing function, there is one of two formats expected: with the limit and without it. The string is parsed, the model is recognized, and then the components of the template tag are passed to the ObjectNode class. In the render method of the node class, we check the manager's name and its method's name. If this is not defined, _default_manager will be used, which is, in most cases, the same as objects. After that, we call the manager method and fall back to empty the QuerySet if the method doesn't exist. If the limit is defined, we resolve the value of it and limit the QuerySet. Lastly, we save the QuerySet to the context variable. See also The Creating a template tag to include a template if it exists recipe The Creating a template tag to parse content as a template recipe The Creating a template tag to modify request query parameters recipe Creating a template tag to parse content as a template In this recipe, we will create a template tag named {% parse %}, which allows you to put template snippets into the database. This is valuable when you want to provide different content for authenticated and non-authenticated users, when you want to include a personalized salutation, or when you don't want to hardcode media paths in the database. Getting ready No surprise, we will start with the utils app that should be installed and ready for custom template tags. How to do it... Template tags consist of two things: the function parsing the arguments of the template tag and the node class that is responsible for the logic of the template tag as well as for the output. Perform the following steps: First, let's create the function parsing the template-tag arguments, as follows: #utils/templatetags/utility_tags.py# -*- coding: UTF-8 -*-from django import templateregister = template.Library()### TAGS ###@register.tagdef parse(parser, token):"""Parses the value as a template and prints it or saves to avariableUsage:{% parse <template_value> [as <variable>] %}Examples:{% parse object.description %}{% parse header as header %}{% parse "{{ MEDIA_URL }}js/" as js_url %}"""bits = token.split_contents()tag_name = bits.pop(0)try:template_value = bits.pop(0)var_name = Noneif len(bits) == 2:bits.pop(0) # remove the word "as"var_name = bits.pop(0)except ValueError:raise template.TemplateSyntaxError, "parse tag requires a following syntax: ""{% parse <template_value> [as <variable>] %}"return ParseNode(template_value, var_name) Then, we create the node class in the same file, as follows: class ParseNode(template.Node):def __init__(self, template_value, var_name):self.template_value = template_valueself.var_name = var_namedef render(self, context):template_value = template.resolve_variable(self.template_value, context)t = template.Template(template_value)context_vars = {}for d in list(context):for var, val in d.items():context_vars[var] = valresult = t.render(template.RequestContext(context['request'], context_vars))if self.var_name:context[self.var_name] = resultreturn ""return result How it works... The {% parse %} template tag allows you to parse a value as a template and to render it immediately or to save it as a context variable. If we have an object with a description field, which can contain template variables or logic, then we can parse it and render it using the following code: {% load utility_tags %}{% parse object.description %} It is also possible to define a value to parse using a quoted string like this: {% load utility_tags %}{% parse "{{ STATIC_URL }}site/img/" as img_path %}<img src="{{ img_path }}someimage.png" alt="" /> Let's have a look at the code of the template tag. The parsing function checks the arguments of the template tag bit by bit. At first, we expect the name parse, then the template value, then optionally the word as, and lastly the context variable name. The template value and the variable name are passed to the ParseNode class. The render method of that class at first resolves the value of the template variable and creates a template object out of it. Then, it renders the template with all the context variables. If the variable name is defined, the result is saved to it; otherwise, the result is shown immediately. See also The Creating a template tag to include a template if it exists recipe The Creating a template tag to load a QuerySet in a template recipe The Creating a template tag to modify request query parameters recipe Creating a template tag to modify request query parameters Django has a convenient and flexible system to create canonical, clean URLs just by adding regular expression rules in the URL configuration files. But there is a lack of built-in mechanisms to manage query parameters. Views such as search or filterable object lists need to accept query parameters to drill down through filtered results using another parameter or to go to another page. In this recipe, we will create a template tag named {% append_to_query %}, which lets you add, change, or remove parameters of the current query. Getting ready Once again, we start with the utils app that should be set in INSTALLED_APPS and should contain the templatetags package. Also, make sure that you have the request context processor set for the TEMPLATE_CONTEXT_PROCESSORS setting, as follows: #settings.pyTEMPLATE_CONTEXT_PROCESSORS = ("django.contrib.auth.context_processors.auth","django.core.context_processors.debug","django.core.context_processors.i18n","django.core.context_processors.media","django.core.context_processors.static","django.core.context_processors.tz","django.contrib.messages.context_processors.messages","django.core.context_processors.request",) How to do it... For this template tag, we will be using the simple_tag decorator that parses the components and requires you to define just the rendering function, as follows: #utils/templatetags/utility_tags.py# -*- coding: UTF-8 -*-import urllibfrom django import templatefrom django.utils.encoding import force_strregister = template.Library()### TAGS ###@register.simple_tag(takes_context=True)def append_to_query(context, **kwargs):""" Renders a link with modified current query parameters """query_params = context['request'].GET.copy()for key, value in kwargs.items():query_params[key] = valuequery_string = u""if len(query_params):query_string += u"?%s" % urllib.urlencode([(key, force_str(value)) for (key, value) inquery_params. iteritems() if value]).replace('&', '&amp;')return query_string How it works... The {% append_to_query %} template tag reads the current query parameters from the request.GET dictionary-like QueryDict object to a new dictionary named query_params, and loops through the keyword parameters passed to the template tag updating the values. Then, the new query string is formed, all spaces and special characters are URL-encoded, and ampersands connecting query parameters are escaped. This new query string is returned to the template. To read more about QueryDict objects, refer to the official Django documentation: https://docs.djangoproject.com/en/1.6/ref/request-response/#querydict-objects Let's have a look at an example of how the {% append_to_query %} template tag can be used. If the current URL is http://127.0.0.1:8000/artists/?category=fine-art&page=1, we can use the following template tag to render a link that goes to the next page: {% load utility_tags %}<a href="{% append_to_query page=2 %}">2</a> The following is the output rendered, using the preceding template tag: <a href="?category=fine-art&amp;page=2">2</a> Or we can use the following template tag to render a link that resets pagination and goes to another category: {% load utility_tags i18n %} <a href="{% append_to_query category="sculpture" page="" %}">{% trans "Sculpture" %}</a> The following is the output rendered, using the preceding template tag: <a href="?category=sculpture">Sculpture</a> See also The Creating a template tag to include a template if it exists recipe The Creating a template tag to load a QuerySet in a template recipe The Creating a template tag to parse content as a template recipe Summary In this article showed you how to create and use your own template filters and tags, as the default Django template system is quite extensive, and there are more things to add for different cases. Resources for Article: Further resources on this subject: Adding a developer with Django forms [Article] So, what is Django? [Article] Django JavaScript Integration: jQuery In-place Editing Using Ajax [Article]
Read more
  • 0
  • 0
  • 5762

article-image-installing-numpy-scipy-matplotlib-ipython
Packt Editorial Staff
12 Oct 2014
7 min read
Save for later

Installing NumPy, SciPy, matplotlib, and IPython

Packt Editorial Staff
12 Oct 2014
7 min read
This article written by Ivan Idris, author of the book, Python Data Analysis, will guide you to install NumPy, SciPy, matplotlib, and IPython. We can find a mind map describing software that can be used for data analysis at https://www.xmind.net/m/WvfC/. Obviously, we can't install all of this software in this article. We will install NumPy, SciPy, matplotlib, and IPython on different operating systems. [box type="info" align="" class="" width=""]Packt has the following books that are focused on NumPy: NumPy Beginner's Guide Second Edition, Ivan Idris NumPy Cookbook, Ivan Idris Learning NumPy Array, Ivan Idris [/box] SciPy is a scientific Python library, which supplements and slightly overlaps NumPy. NumPy and SciPy, historically shared their codebase but were later separated. matplotlib is a plotting library based on NumPy. IPython provides an architecture for interactive computing. The most notable part of this project is the IPython shell. Software used The software used in this article is based on Python, so it is required to have Python installed. On some operating systems, Python is already installed. You, however, need to check whether the Python version is compatible with the software version you want to install. There are many implementations of Python, including commercial implementations and distributions. [box type="note" align="" class="" width=""]You can download Python from https://www.python.org/download/. On this website, we can find installers for Windows and Mac OS X, as well as source archives for Linux, Unix, and Mac OS X.[/box] The software we will install has binary installers for Windows, various Linux distributions, and Mac OS X. There are also source distributions, if you prefer that. You need to have Python 2.4.x or above installed on your system. Python 2.7.x is currently the best Python version to have because most Scientific Python libraries support it. Python 2.7 will be supported and maintained until 2020. After that, we will have to switch to Python 3. Installing software and setup on Windows Installing on Windows is, fortunately, a straightforward task that we will cover in detail. You only need to download an installer, and a wizard will guide you through the installation steps. We will give steps to install NumPy here. The steps to install the other libraries are similar. The actions we will take are as follows: Download installers for Windows from the SourceForge website (refer to the following table). The latest release versions may change, so just choose the one that fits your setup best. Library URL Latest Version NumPy http://sourceforge.net/projects/numpy/files/ 1.8.1 SciPy http://sourceforge.net/projects/scipy/files/ 0.14.0 matplotlib http://sourceforge.net/projects/matplotlib/files/ 1.3.1 IPython http://archive.ipython.org/release/ 2.0.0 Choose the appropriate version. In this example, we chose numpy-1.8.1-win32-superpack-python2.7.exe. Open the EXE installer by double-clicking on it. Now, we can see a description of NumPy and its features. Click on the Next button.If you have Python installed, it should automatically be detected. If it is not detected, maybe your path settings are wrong. Click on the Next button if Python is found; otherwise, click on the Cancel button and install Python (NumPy cannot be installed without Python). Click on the Next button. This is the point of no return. Well, kind of, but it is best to make sure that you are installing to the proper directory and so on and so forth. Now the real installation starts. This may take a while. [box type="note" align="" class="" width=""]The situation around installers is rapidly evolving. Other alternatives exist in various stage of maturity (see https://www.scipy.org/install.html). It might be necessary to put the msvcp71.dll file in your C:Windowssystem32 directory. You can get it from http://www.dll-files.com/dllindex/dll-files.shtml?msvcp71.[/box] Installing software and setup on Linux Installing the recommended software on Linux depends on the distribution you have. We will discuss how you would install NumPy from the command line, although, you could probably use graphical installers; it depends on your distribution (distro). The commands to install matplotlib, SciPy, and IPython are the same – only the package names are different. Installing matplotlib, SciPy, and IPython is recommended, but optional. Most Linux distributions have NumPy packages. We will go through the necessary steps for some of the popular Linux distros: Run the following instructions from the command line for installing NumPy on Red Hat: $ yum install python-numpy To install NumPy on Mandriva, run the following command-line instruction: $ urpmi python-numpy To install NumPy on Gentoo run the following command-line instruction: $ sudo emerge numpy To install NumPy on Debian or Ubuntu, we need to type the following: $ sudo apt-get install python-numpy The following table gives an overview of the Linux distributions and corresponding package names for NumPy, SciPy, matplotlib, and IPython. Linux distribution NumPy SciPy matplotlib IPython Arch Linux python-numpy python-scipy python-matplotlib Ipython Debian python-numpy python-scipy python-matplotlib Ipython Fedora numpy python-scipy python-matplotlib Ipython Gentoo dev-python/numpy scipy matplotlib ipython OpenSUSE python-numpy, python-numpy-devel python-scipy python-matplotlib ipython Slackware numpy scipy matplotlib ipython Installing software and setup on Mac OS X You can install NumPy, matplotlib, and SciPy on the Mac with a graphical installer or from the command line with a port manager such as MacPorts, depending on your preference. Prerequisite is to install XCode as it is not part of OS X releases. We will install NumPy with a GUI installer using the following steps: We can get a NumPy installer from the SourceForge website http://sourceforge.net/projects/numpy/files/. Similar files exist for matplotlib and SciPy. Just change numpy in the previous URL to scipy or matplotlib. IPython didn't have a GUI installer at the time of writing. Download the appropriate DMG file usually the latest one is the best.Another alternative is the SciPy Superpack (https://github.com/fonnesbeck/ScipySuperpack). Whichever option you choose it is important to make sure that updates which impact the system Python library don't negatively influence already installed software by not building against the Python library provided by Apple. Open the DMG file (in this example, numpy-1.8.1-py2.7-python.org-macosx10.6.dmg). Double-click on the icon of the opened box, the one having a subscript that ends with .mpkg. We will be presented with the welcome screen of the installer. Click on the Continue button to go to the Read Me screen, where we will be presented with a short description of NumPy. Click on the Continue button to the License the screen. Read the license, click on the Continue button and then on the Accept button, when prompted to accept the license. Continue through the next screens and click on the Finish button at the end. Alternatively, we can install NumPy, SciPy, matplotlib, and IPython through the MacPorts route, with Fink or Homebrew. The following installation steps shown, installs all these packages. [box type="info" align="" class="" width=""]For installing with MacPorts, type the following command: sudo port install py-numpy py-scipy py-matplotlib py- ipython [/box] Installing with setuptools If you have pip you can install NumPy, SciPy, matplotlib and IPython with the following commands. pip install numpy pip install scipy pip install matplotlib pip install ipython It may be necessary to prepend sudo to these commands, if your current user doesn't have sufficient rights on your system. Summary In this article, we installed NumPy, SciPy, matplotlib and IPython on Windows, Mac OS X and Linux. Resources for Article: Further resources on this subject: Plotting Charts with Images and Maps Importing Dynamic Data Python 3: Designing a Tasklist Application
Read more
  • 0
  • 0
  • 64635

article-image-using-sensors
Packt
10 Oct 2014
25 min read
Save for later

Using Sensors

Packt
10 Oct 2014
25 min read
In this article by Leon Anavi, author of the Tizen Cookbook, we will cover the following topics: Using location-based services to display current location Getting directions Geocoding Reverse geocoding Calculating distance Detecting device motion Detecting device orientation Using the Vibration API (For more resources related to this topic, see here.) The data provided by the hardware sensors of Tizen devices can be useful for many mobile applications. In this article, you will learn how to retrieve the geographic location of Tizen devices using the assisted GPS, to detect changes of the device orientation and motion as well as how to integrate map services into Tizen web applications. Most of the examples related to maps and navigation use Google APIs. Other service providers such as Nokia HERE, OpenStreetMap, and Yandex also offer APIs with similar capabilities and can be used as an alternative to Google in Tizen web applications. It was announced that Nokia HERE joined the Tizen association at the time of writing this book. Some Tizen devices will be shipped with built-in navigation applications powered by Nokia HERE. The smart watch Gear S is the first Tizen wearable device from Samsung that comes of the box with an application called Navigator, which is developed with Nokia HERE. Explore the full capabilities of Nokia HERE JavaScript APIs if you are interested in their integration in your Tizen web application at https://developer.here.com/javascript-apis. OpenStreetMap also deserves special attention because it is a high quality platform and very successful community-driven project. The main advantage of OpenStreetMap is that its usage is completely free. The recipe about Reverse geocoding in this article demonstrates address lookup using two different approaches: through Google and through OpenStreetMap API. Using location-based services to display current location By following the provided example in this recipe, you will master the HTML5 Geolocation API and learn how to retrieve the coordinates of the current location of a device in a Tizen web application. Getting ready Ensure that the positioning capabilities are turned on. On a Tizen device or Emulator, open Settings, select Locations, and turn on both GPS (if it is available) and Network position as shown in the following screenshot: Enabling GPS and network position from Tizen Settings How to do it... Follow these steps to retrieve the location in a Tizen web application: Implement JavaScript for handling errors: function showError(err) { console.log('Error ' + err.code + ': ' + err.message); } Implement JavaScript for processing the retrieved location: function showLocation(location) { console.log('latitude: ' + location.coords.longitude + '    longitude: ' + location.coords.longitude); } Implement a JavaScript function that searches for the current position using the HTML5 Geolocation API: function retrieveLocation() { if (navigator.geolocation) {    navigator.geolocation.getCurrentPosition(showLocation,      showError); } } At an appropriate place in the source code of the application, invoke the function created in the previous step: retrieveLocation(); How it works The getCurrentPosition() method of the HTML5 Geolocation API is used in the retrieveLocation() function to retrieve the coordinates of the current position of the device. The functions showLocation() and showError() are provided as callbacks, which are invoked on success or failure. An instance of the Position interface is provided as an argument to showLocation(). This interface has two properties: coords: This specifies an object that defines the retrieved position timestamp: This specifies the date and time when the position has been retrieved The getCurrentPosition() method accepts an instance of the PositionOptions interface as a third optional argument. This argument should be used for setting specific options such as enableHighAccuracy, timeout, and maximumAge. Explore the Geolocation API specification if you are interested in more details regarding the attributes of the discussed interface at http://www.w3.org/TR/geolocation-API/#position-options. There is no need to add any specific permissions explicitly in config.xml. When an application that implements the code from this recipe, is launched for the first time, it will ask for permission to access the location, as shown in the following screenshot: A request to access location in Tizen web application If you are developing a location-based application and want to debug it using the Tizen Emulator, use the Event Injector to set the position. There's more... A map view provided by Google Maps JavaScript API v3 can be easily embedded into a Tizen web application. An internet connection is required to use the API, but there is no need to install an additional SDK or tools from Google. Follow these instructions to display a map and a marker: Make sure that the application can access the Google API. For example, you can enable access to any website by adding the following line to config.xml: <access origin="*" subdomains="true"></access> Visit https://code.google.com/apis/console to get the API keys. Click on Services and activate Google Maps API v3. After that, click on API and copy Key for browser apps. Its value will be used in the source code of the application. Implement the following source code to show a map inside div with the ID map-canvas: <style type="text/css"> #map-canvas { width: 320px; height: 425px; } </style> <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<API Key>&sensor=false"></script> Replace <API Key> in the line above with the value of the key obtained on the previous step. <script type="text/javascript"> function initialize(nLatitude, nLongitude) { var mapOptions = {    center: new google.maps.LatLng(nLatitude, nLongitude),    zoom: 14 }; var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions); var marker = new google.maps.Marker({    position: new google.maps.LatLng(nLatitude,      nLongitude),    map: map }); } </script> In the HTML of the application, create the following div element: <div id="map-canvas"></div> Provide latitude and longitude to the function and execute it at an appropriate location. For example, these are the coordinates of a location in Westminster, London: initialize(51.501725, -0.126109); The following screenshot demonstrates a Tizen web application that has been created by following the preceding guidelines: Google Map in Tizen web application Combine the tutorial from the How to do it section of the recipe with these instructions to display a map with the current location. See also A source code of a simple Tizen web application is provided alongside the book following the tutorial from this recipe. Feel free to use it as you wish. More details are available in the W3C specification of the HTML5 Geolocation API at http://www.w3.org/TR/geolocation-API/. To learn more details and to explore the full capabilities of the Google Maps JavaScript API v3, please visit https://developers.google.com/maps/documentation/javascript/tutorial. Getting directions Navigation is another common task for mobile applications. The Google Directions API allows web and mobile developers to retrieve a route between locations by sending an HTTP request. It is mandatory to specify an origin and a destination, but it is also possible to set way points. All locations can be provided either by exact coordinates or by address. An example for getting directions and to reach a destination on foot is demonstrated in this recipe. Getting ready Before you start with the development, register an application and obtain API keys: Log in to Google Developers Console at https://code.google.com/apis/console. Click on Services and turn on Directions API. Click on API Access and get the value of Key for server apps, which should be used in all requests from your Tizen web application to the API. For more information about the API keys for the Directions API, please visit https://developers.google.com/maps/documentation/directions/#api_key. How to do it... Use the following source code to retrieve and display step-by-step instructions on how to walk from one location to another using the Google Directions API: Allow the application to access websites by adding the following line to config.xml: <access origin="*" subdomains="true"></access> Create an HTML unordered list: <ul id="directions" data-role="listview"></ul> Create JavaScript that will load retrieved directions: function showDirections(data) { if (!data || !data.routes || (0 == data.routes.length)) {    console.log('Unable to provide directions.');    return; } var directions = data.routes[0].legs[0].steps; for (nStep = 0; nStep < directions.length; nStep++) {     var listItem = $('<li>').append($( '<p>'      ).append(directions[nStep].html_instructions));    $('#directions').append(listItem); } $('#directions').listview('refresh'); } Create a JavaScript function that sends an asynchronous HTTP (AJAX) request to the Google Maps API to retrieve directions: function retrieveDirection(sLocationStart, sLocationEnd){ $.ajax({    type: 'GET',    url: 'https://maps.googleapis.com/maps/api/directions/json?',    data: { origin: sLocationStart,        destination: sLocationEnd,        mode: 'walking',        sensor: 'true',        key: '<API key>' }, Do not forget to replace <API key> with the Key for server apps value provided by Google for the Directions API. Please note that a similar key has to be set to the source code in the subsequent recipes that utilize Google APIs too:    success : showDirections,    error : function (request, status, message) {    console.log('Error');    } }); } Provide start and end locations as arguments and execute the retrieveDirection() function. For example: retrieveDirection('Times Square, New York, NY, USA', 'Empire State Building, 350 5th Avenue, New York, NY 10118, USA'); How it works The first mandatory step is to allow access to the Tizen web application to Google servers. After that, an HTML unordered list with ID directions is constructed. An origin and destination is provided to the JavaScript function retrieveDirections(). On success, the showDirections() function is invoked as a callback and it loads step-by-step instructions on how to move from the origin to the destination. The following screenshot displays a Tizen web application with guidance on how to walk from Times Square in New York to the Empire State Building: The Directions API is quite flexible. The mandatory parameters are origin, destination, and sensor. Numerous other options can be configured at the HTTP request using different parameters. To set the desired transport, use the parameter mode, which has the following options: driving walking bicycling transit (for getting directions using public transport) By default, if the mode is not specified, its value will be set to driving. The unit system can be configured through the parameter unit. The options metric and imperial are available. The developer can also define restrictions using the parameter avoid and the addresses of one or more directions points at the waypoints parameter. A pipe (|) is used as a symbol for separation if more than one address is provided. There's more... An application with similar features for getting directions can also be created using services from Nokia HERE. The REST API can be used in the same way as Google Maps API. Start by acquiring the credentials at http://developer.here.com/get-started. An asynchronous HTTP request should be sent to retrieve directions. Instructions on how to construct the request to the REST API are provided in its documentation at https://developer.here.com/rest-apis/documentation/routing/topics/request-constructing.html. The Nokia HERE JavaScript API is another excellent solution for routing. Make instances of classes Display and Manager provided by the API to create a map and a routing manager. After that, create a list of way points whose coordinates are defined by an instance of the Coordinate class. Refer to the following example provided by the user's guide of the API to learn details at https://developer.here.com/javascript-apis/documentation/maps/topics/routing.html. The full specifications about classes Display, Manager, and Coordinate are available at the following links: https://developer.here.com/javascript-apis/documentation/maps/topics_api_pub/nokia.maps.map.Display.html https://developer.here.com/javascript-apis/documentation/maps/topics_api_pub/nokia.maps.routing.Manager.html https://developer.here.com/javascript-apis/documentation/maps/topics_api_pub/nokia.maps.geo.Coordinate.html See also All details, options, and returned results from the Google Directions API are available at https://developers.google.com/maps/documentation/directions/. Geocoding Geocoding is the process of retrieving geographical coordinates associated with an address. It is often used in mobile applications that use maps and provide navigation. In this recipe, you will learn how to convert an address to longitude and latitude using JavaScript and AJAX requests to the Google Geocoding API. Getting ready You must obtain keys before you can use the Geocoding API in a Tizen web application: Visit Google Developers Console at https://code.google.com/apis/console. Click on Services and turn on Geocoding API. Click on API Access and get the value of Key for server apps. Use it in all requests from your Tizen web application to the API. For more details regarding the API keys for the Geocoding API, visit https://developers.google.com/maps/documentation/geocoding/#api_key. How to do it... Follow these instructions to retrieve geographic coordinates of an address in a Tizen web application using the Google Geocoding API: Allow the application to access websites by adding the following line to config.xml: <access origin="*" subdomains="true"></access> Create a JavaScript function to handle results provided by the API: function retrieveCoordinates(data) { if (!data || !data.results || (0 == data.results.length)) {    console.log('Unable to retrieve coordinates');    return; } var latitude = data.results[0].geometry.location.lat; var longitude = data.results[0].geometry.location.lng; console.log('latitude: ' + latitude + ' longitude: ' +    longitude); } Create a JavaScript function that sends a request to the API: function geocoding(address) { $.ajax({    type: 'GET',    url: 'https://maps.googleapis.com/maps/api/geocode/json?',    data: { address: address,      sensor: 'true',      key: '<API key>' }, As in the previous recipes, you should again replace <API key> with the Key for server apps value provided by Google for the Geocoding API.    success : retrieveCoordinates,    error : function (request, status, message) {    console.log('Error: ' + message);    } }); } Provide the address as an argument to the geocoding() function and invoke it. For example: geocoding('350 5th Avenue, New York, NY 10118, USA'); How it works The address is passed as an argument to the geocoding() function, which sends a request to the URL of Google Geocoding API. The URL specifies that the returned result should be serialized as JSON. The parameters of the URL contain information about the address and the API key. Additionally, there is a parameter that indicates whether the device has a sensor. In general, Tizen mobile devices are equipped with GPS so the parameter sensor is set to true. A successful response from the API is handled by the retrieveCoordinates() function, which is executed as a callback. After processing the data, the code snippet in this recipe prints the retrieved coordinates at the console. For example, if we provide the address of the Empire State Building to the geocoding() function on success, the following text will be printed: latitude: 40.7481829 longitude: -73.9850635. See also Explore the Google Geocoding API documentation to learn the details regarding the usage of the API and all of its parameters at https://developers.google.com/maps/documentation/geocoding/#GeocodingRequests. Nokia HERE provides similar features. Refer to the documentation of its Geocoder API to learn how to create the URL of a request to it at https://developer.here.com/rest-apis/documentation/geocoder/topics/request-constructing.html. Reverse geocoding Reverse geocoding, also known as address lookup, is the process of retrieving an address that corresponds to a location described with geographic coordinates. The Google Geocoding API provides methods for both geocoding as well as reverse geocoding. In this recipe, you will learn how to find the address of a location based on its coordinates using the Google API as well as an API provided by OpenStreetMap. Getting ready Same keys are required for geocoding and reverse geocoding. If you have already obtained a key for the previous recipe, you can directly use it here again. Otherwise, you can perform the following steps: Visit Google Developers Console at https://code.google.com/apis/console. Go to Services and turn on Geocoding API. Select API Access, locate the value of Key for server apps, and use it in all requests from the Tizen web application to the API. If you need more information about the Geocoding API keys, visit https://developers.google.com/maps/documentation/geocoding/#api_key. How to do it... Follow the described algorithm to retrieve an address based on geographic coordinates using the Google Maps Geocoding API: Allow the application to access websites by adding the following line to config.xml: <access origin="*" subdomains="true"></access> Create a JavaScript function to handle the data provided for a retrieved address: function retrieveAddress(data) { if (!data || !data.results || (0 == data.results.length)) {    console.log('Unable to retrieve address');    return; } var sAddress = data.results[0].formatted_address; console.log('Address: ' + sAddress); } Implement a function that performs a request to Google servers to retrieve an address based on latitude and longitude: function reverseGeocoding(latitude, longitude) { $.ajax({    type: 'GET',    url: 'https://maps.googleapis.com/maps/api/geocode/json?',    data: { latlng: latitude+','+longitude,        sensor: 'true',        key: '<API key>' }, Pay attention that <API key> has to be replaced with the Key for server apps value provided by Google for the Geocoding API:    success : retrieveAddress,    error : function (request, status, message) {    console.log('Error: ' + message);    } }); } Provide coordinates as arguments of function and execute it, for example: reverseGeocoding('40.748183', '-73.985064'); How it works If an application developed using the preceding source code invokes the reverseGeocoding() function with latitude 40.748183 and longitude -73.985064, the printed result at the console will be: 350 5th Avenue, New York, NY 10118, USA. By the way, as in the previous recipe, the address corresponds to the location of the Empire State Building in New York. The reverseGeocoding() function sends an AJAX request to the API. The parameters at the URL specify that the response must be formatted as JSON. The longitude and latitude of the location are divided by commas and set as a value of the latlng parameter in the URL. There's more... OpenStreetMap also provides a reverse geocoding services. For example, the following URL will return a JSON result of a location with the latitude 40.7481829 and longitude -73.9850635: http://nominatim.openstreetmap.org/reverse?format=json&lat=40.7481829&lon=-73.9850635 The main advantage of OpenStreetMap is that it is an open project with a great community. Its API for reverse geocoding does not require any keys and it can be used for free. Leaflet is a popular open source JavaScript library based on OpenStreetMap optimized for mobile devices. It is well supported and easy to use, so you may consider integrating it in your Tizen web applications. Explore its features at http://leafletjs.com/features.html. See also All details regarding the Google Geocoding API are available at https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding If you prefer to user the API provided by OpenStreetMap, please have a look at http://wiki.openstreetmap.org/wiki/Nominatim#Reverse_Geocoding_.2F_Address_lookup Calculating distance This recipe is dedicated to a method for calculating the distance between two locations. The Google Directions API will be used again. Unlike the Getting directions recipe, this time only the information about the distance will be processed. Getting ready Just like the other recipe related to the Google API, in this case, the developer must obtain the API keys before the start of the development. Please follow these instructions to register and get an appropriate API key: Visit Google Developers Console at https://code.google.com/apis/console. Click on Services and turn on Geocoding API. Click on API Access and save the value of Key for server apps. Use it in all requests from your Tizen web application to the API. If you need more information about the API keys for Directions API, visit https://developers.google.com/maps/documentation/directions/#api_key. How to do it... Follow these steps to calculate the distance between two locations: Allow the application to access websites by adding the following line to config.xml: <access origin="*" subdomains="true"></access> Implement a JavaScript function that will process the retrieved data: function retrieveDistance(data) { if (!data || !data.routes || (0 == data.routes.length)) {    console.log('Unable to retrieve distance');    return; } var sLocationStart =    data.routes[0].legs[0].start_address; var sLocationEnd = data.routes[0].legs[0].end_address; var sDistance = data.routes[0].legs[0].distance.text; console.log('The distance between ' + sLocationStart + '    and ' + sLocationEnd + ' is: ' +    data.routes[0].legs[0].distance.text); } Create a JavaScript function that will request directions using the Google Maps API: function checkDistance(sStart, sEnd) { $.ajax({    type: 'GET',    url: 'https://maps.googleapis.com/maps/api/directions/json?',    data: { origin: sStart,        destination: sEnd,        sensor: 'true',        units: 'metric',        key: '<API key>' }, Remember to replace <API key> with the Key for server apps value provided by Google for the Direction API:        success : retrieveDistance,        error : function (request, status, message) {        console.log('Error: ' + message);        }    }); } Execute the checkDistance() function and provide the origin and the destination as arguments, for example: checkDistance('Plovdiv', 'Burgas'); Geographical coordinates can also be provided as arguments to the function checkDistance(). For example, let's calculate the same distances but this time by providing the latitude and longitude of locations in the Bulgarian cities Plovdiv and Burgas: checkDistance('42.135408,24.74529', '42.504793,27.462636'); How it works The checkDistance() function sends data to the Google Directions API. It sets the origin, the destination, the sensor, the unit system, and the API key as parameters of the URL. The result returned by the API is provided as JSON, which is handled in the retriveDistance() function. The output in the console of the preceding example, which retrieves the distance between the Bulgarian cities Plovdiv and Burgas, is The distance between Plovdiv, Bulgaria and Burgas, Bulgaria is: 253 km. See also For all details about the Directions API as well as a full description of the returned response, visit https://developers.google.com/maps/documentation/directions/. Detecting device motion This recipe offers a tutorial on how to detect and handle device motion in Tizen web applications. No specific Tizen APIs will be used. The source code in this recipe relies on the standard W3C DeviceMotionEvent, which is supported by Tizen web applications as well as any modern web browser. How to do it... Please follow these steps to detect device motion and display its acceleration in a Tizen web application: Create HTML components to show device acceleration, for example: <p>X: <span id="labelX"></span></p> <p>Y: <span id="labelY"></span></p> <p>Z: <span id="labelZ"></span></p> Create a JavaScript function to handle errors: function showError(err) { console.log('Error: ' + err.message); } Create a JavaScript function that handles motion events: function motionDetected(event) { var acc = event.accelerationIncludingGravity; var sDeviceX = (acc.x) ? acc.x.toFixed(2) : '?'; var sDeviceY = (acc.y) ? acc.y.toFixed(2) : '?'; var sDeviceZ = (acc.z) ? acc.z.toFixed(2) : '?'; $('#labelX').text(sDeviceX); $('#labelY').text(sDeviceY); $('#labelZ').text(sDeviceZ); } Create a JavaScript function that starts a listener for motion events: function deviceMotion() { try {    if (!window.DeviceMotionEvent) {      throw new Error('device motion not supported.');    }    window.addEventListener('devicemotion', motionDetected,      false); } catch (err) {    showError(err); } } Invoke a function at an appropriate location of the source code of the application: deviceMotion(); How it works The deviceMotion() function registers an event listener that invokes the motionDetected() function as a callback when device motion event is detected. All errors, including an error if DeviceMotionEvent is not supported, are handled in the showError() function. As shown in the following screenshot, the motionDetected() function loads the data of the properties of DeviceMotionEvent into the HTML5 labels that were created in the first step. The results are displayed using standard units for acceleration according to the international system of units (SI)—metres per second squared (m/s2). The JavaScript method toFixed() is invoked to convert the result to a string with two decimals: A Tizen web application that detects device motion See also Notice that the device motion event specification is part of the DeviceOrientationEvent specification. Both are still in draft. The latest published version is available at http://www.w3.org/TR/orientation-event/. The source code of a sample Tizen web application that detects device motion is provided along with the book. You can import the project of the application into the Tizen IDE and explore it. Detecting device orientation In this recipe, you will learn how to monitor changes of the device orientation using the HTML5 DeviceOrientation event as well as get the device orientation using the Tizen SystemInfo API. Both methods for retrieving device orientation have advantages and work in Tizen web applications. It is up to the developer to decide which approach is more suitable for their application. How to do it... Perform the following steps to register a listener and handle device orientation events in your Tizen web application: Create a JavaScript function to handle errors: function showError(err) { console.log('Error: ' + err.message); } Create a JavaScript function that handles change of the orientation: function orientationDetected(event) { console.log('absolute: ' + event.absolute); console.log('alpha: ' + event.alpha); console.log('beta: ' + event.beta); console.log('gamma: ' + event.gamma); } Create a JavaScript function that adds a listener for the device orientation: function deviceOrientation() { try {    if (!window.DeviceOrientationEvent) {      throw new Error('device motion not supported.');    }    window.addEventListener('deviceorientation',      orientationDetected, false); } catch (err) {    showError(err); } } Execute the JavaScript function to start listening for device orientation events: deviceOrientation(); How it works If DeviceOrientationEvent is supported, the deviceOrientation() function binds the event to the orientationDetected() function, which is invoked as a callback only on success. The showError() function will be executed only if a problem occurs. An instance of the DeviceOrientationEvent interface is provided as an argument of the orientationDetected() function. In the preceding code snippet, the values of its four read-only properties absolute (Boolean value, true if the device provides orientation data absolutely), alpha (motion around the z axis), beta (motion around the x-axis), and gamma (motion around the y axis) are printed in the console. There's more... There is an easier way to determine whether a Tizen device is in landscape or portrait mode. In a Tizen web application, for this case, it is recommended to use the SystemInfo API. The following code snippet retrieves the device orientation: function onSuccessCallback(orientation) { console.log("Device orientation: " + orientation.status); } function onErrorCallback(error) { console.log("Error: " + error.message); }  tizen.systeminfo.getPropertyValue("DEVICE_ORIENTATION", onSuccessCallback, onErrorCallback); The status of the orientation can be one of the following values: PORTRAIT_PRIMARY PORTRAIT_SECONDARY LANDSCAPE_PRIMARY LANDSCAPE_SECONDARY See also The DeviceOrientationEvent specification is still a draft. The latest published version is available at http://www.w3.org/TR/orientation-event/. For more information on the Tizen SystemInfo API, visit https://developer.tizen.org/dev-guide/2.2.1/org.tizen.web.device.apireference/tizen/systeminfo.html. Using the Vibration API Tizen is famous for its excellent support of HTML5 and W3C APIs. The standard Vibration API is also supported and it can be used in Tizen web applications. This recipe offers code snippets on how to activate vibration on a Tizen device. How to do it... Use the following code snippet to activate the vibration of the device for three seconds: if (navigator.vibrate) { navigator.vibrate(3000); } To cancel an ongoing vibration, just call the vibrate() method again with zero as a value of its argument: if (navigator.vibrate) { navigator.vibrate(0); } Alternatively, the vibration can be canceled by passing an empty array to the same method: navigator.vibrate([]); How it works The W3C Vibration API is used through the JavaScript object navigator. Its vibrate() method expects either a single value or an array of values. All values must be specified in milliseconds. The value provided to the vibrate() method in the preceding example is 3000 because 3 seconds is equal to 3000 milliseconds. There's more... The W3C Vibration API allows advanced tuning of the device vibration. A list of time intervals (with values in milliseconds), during which the device will vibrate, can be specified as an argument of the vibrate() method. For example, the following code snippet will make the device to vibrate for 100 ms, stand still for 3 seconds, and then again vibrate, but this time just for 50 ms: if (navigator.vibrate) { navigator.vibrate([100, 3000, 50]); } See also For more information on the vibration capabilities and the API usage, visit http://www.w3.org/TR/vibration/. Tizen native applications for the mobile profile have exposure to additional APIs written in C++ for light and proximity sensors. Explore the source code of the sample native application SensorApp which is provided with the Tizen SDK to learn how to use these sensors. More information about them is available at https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/guide/uix/light_sensor.htm and https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/guide/uix/proximity_sensor.htm. Summary In this article, we learned the details of various hardware sensors such as the GPS, accelerometer, and gyroscope sensor. The main focus of this article was on location-based services, maps, and navigation. Resources for Article: Further resources on this subject: Major SDK components [article] Getting started with Kinect for Windows SDK Programming [article] https://www.packtpub.com/books/content/cordova-plugins [article]
Read more
  • 0
  • 0
  • 7082

article-image-indexing-and-performance-tuning
Packt
10 Oct 2014
40 min read
Save for later

Indexing and Performance Tuning

Packt
10 Oct 2014
40 min read
In this article by Hans-Jürgen Schönig, author of the book PostgreSQL Administration Essentials, you will be guided through PostgreSQL indexing, and you will learn how to fix performance issues and find performance bottlenecks. Understanding indexing will be vital to your success as a DBA—you cannot count on software engineers to get this right straightaway. It will be you, the DBA, who will face problems caused by bad indexing in the field. For the sake of your beloved sleep at night, this article is about PostgreSQL indexing. (For more resources related to this topic, see here.) Using simple binary trees In this section, you will learn about simple binary trees and how the PostgreSQL optimizer treats the trees. Once you understand the basic decisions taken by the optimizer, you can move on to more complex index types. Preparing the data Indexing does not change user experience too much, unless you have a reasonable amount of data in your database—the more data you have, the more indexing can help to boost things. Therefore, we have to create some simple sets of data to get us started. Here is a simple way to populate a table: test=# CREATE TABLE t_test (id serial, name text);CREATE TABLEtest=# INSERT INTO t_test (name) SELECT 'hans' FROM   generate_series(1, 2000000);INSERT 0 2000000test=# INSERT INTO t_test (name) SELECT 'paul' FROM   generate_series(1, 2000000);INSERT 0 2000000 In our example, we created a table consisting of two columns. The first column is simply an automatically created integer value. The second column contains the name. Once the table is created, we start to populate it. It's nice and easy to generate a set of numbers using the generate_series function. In our example, we simply generate two million numbers. Note that these numbers will not be put into the table; we will still fetch the numbers from the sequence using generate_series to create two million hans and rows featuring paul, shown as follows: test=# SELECT * FROM t_test LIMIT 3;id | name----+------1 | hans2 | hans3 | hans(3 rows) Once we create a sufficient amount of data, we can run a simple test. The goal is to simply count the rows we have inserted. The main issue here is: how can we find out how long it takes to execute this type of query? The timing command will do the job for you: test=# timingTiming is on. As you can see, timing will add the total runtime to the result. This makes it quite easy for you to see if a query turns out to be a problem or not: test=# SELECT count(*) FROM t_test;count---------4000000(1 row)Time: 316.628 ms As you can see in the preceding code, the time required is approximately 300 milliseconds. This might not sound like a lot, but it actually is. 300 ms means that we can roughly execute three queries per CPU per second. On an 8-Core box, this would translate to roughly 25 queries per second. For many applications, this will be enough; but do you really want to buy an 8-Core box to handle just 25 concurrent users, and do you want your entire box to work just on this simple query? Probably not! Understanding the concept of execution plans It is impossible to understand the use of indexes without understanding the concept of execution plans. Whenever you execute a query in PostgreSQL, it generally goes through four central steps, described as follows: Parser: PostgreSQL will check the syntax of the statement. Rewrite system: PostgreSQL will rewrite the query (for example, rules and views are handled by the rewrite system). Optimizer or planner: PostgreSQL will come up with a smart plan to execute the query as efficiently as possible. At this step, the system will decide whether or not to use indexes. Executor: Finally, the execution plan is taken by the executor and the result is generated. Being able to understand and read execution plans is an essential task of every DBA. To extract the plan from the system, all you need to do is use the explain command, shown as follows: test=# explain SELECT count(*) FROM t_test;                             QUERY PLAN                            ------------------------------------------------------Aggregate (cost=71622.00..71622.01 rows=1 width=0)   -> Seq Scan on t_test (cost=0.00..61622.00                         rows=4000000 width=0)(2 rows)Time: 0.370 ms In our case, it took us less than a millisecond to calculate the execution plan. Once you have the plan, you can read it from right to left. In our case, PostgreSQL will perform a sequential scan and aggregate the data returned by the sequential scan. It is important to mention that each step is assigned to a certain number of costs. The total cost for the sequential scan is 61,622 penalty points (more details about penalty points will be outlined a little later). The overall cost of the query is 71,622.01. What are costs? Well, costs are just an arbitrary number calculated by the system based on some rules. The higher the costs, the slower a query is expected to be. Always keep in mind that these costs are just a way for PostgreSQL to estimate things—they are in no way a reliable number related to anything in the real world (such as time or amount of I/O needed). In addition to the costs, PostgreSQL estimates that the sequential scan will yield around four million rows. It also expects the aggregation to return just a single row. These two estimates happen to be precise, but it is not always so. Calculating costs When in training, people often ask how PostgreSQL does its cost calculations. Consider a simple example like the one we have next. It works in a pretty simple way. Generally, there are two types of costs: I/O costs and CPU costs. To come up with I/O costs, we have to figure out the size of the table we are dealing with first: test=# SELECT pg_relation_size('t_test'),   pg_size_pretty(pg_relation_size('t_test'));pg_relation_size | pg_size_pretty------------------+----------------       177127424 | 169 MB(1 row) The pg_relation_size command is a fast way to see how large a table is. Of course, reading a large number (many digits) is somewhat hard, so it is possible to fetch the size of the table in a much prettier format. In our example, the size is roughly 170 MB. Let's move on now. In PostgreSQL, a table consists of 8,000 blocks. If we divide the size of the table by 8,192 bytes, we will end up with exactly 21,622 blocks. This is how PostgreSQL estimates I/O costs of a sequential scan. If a table is read completely, each block will receive exactly one penalty point, or any number defined by seq_page_cost: test=# SHOW seq_page_cost;seq_page_cost---------------1(1 row) To count this number, we have to send four million rows through the CPU (cpu_tuple_cost), and we also have to count these 4 million rows (cpu_operator_cost). So, the calculation looks like this: For the sequential scan: 21622*1 + 4000000*0.01 (cpu_tuple_cost) = 61622 For the aggregation: 61622 + 4000000*0.0025 (cpu_operator_cost) = 71622 This is exactly the number that we see in the plan. Drawing important conclusions Of course, you will never do this by hand. However, there are some important conclusions to be drawn: The cost model in PostgreSQL is a simplification of the real world The costs can hardly be translated to real execution times The cost of reading from a slow disk is the same as the cost of reading from a fast disk It is hard to take caching into account If the optimizer comes up with a bad plan, it is possible to adapt the costs either globally in postgresql.conf, or by changing the session variables, shown as follows: test=# SET seq_page_cost TO 10;SET This statement inflated the costs at will. It can be a handy way to fix the missed estimates, leading to bad performance and, therefore, to poor execution times. This is what the query plan will look like using the inflated costs: test=# explain SELECT count(*) FROM t_test;                     QUERY PLAN                              -------------------------------------------------------Aggregate (cost=266220.00..266220.01 rows=1 width=0)   -> Seq Scan on t_test (cost=0.00..256220.00          rows=4000000 width=0)(2 rows) It is important to understand the PostgreSQL code model in detail because many people have completely wrong ideas about what is going on inside the PostgreSQL optimizer. Offering a basic explanation will hopefully shed some light on this important topic and allow administrators a deeper understanding of the system. Creating indexes After this introduction, we can deploy our first index. As we stated before, runtimes of several hundred milliseconds for simple queries are not acceptable. To fight these unusually high execution times, we can turn to CREATE INDEX, shown as follows: test=# h CREATE INDEXCommand:     CREATE INDEXDescription: define a new indexSyntax:CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ name ]ON table_name [ USING method ]   ( { column_name | ( expression ) }[ COLLATE collation ] [ opclass ][ ASC | DESC ] [ NULLS { FIRST | LAST } ]   [, ...] )   [ WITH ( storage_parameter = value [, ... ] ) ]   [ TABLESPACE tablespace_name ]   [ WHERE predicate ] In the most simplistic case, we can create a normal B-tree index on the ID column and see what happens: test=# CREATE INDEX idx_id ON t_test (id);CREATE INDEXTime: 3996.909 ms B-tree indexes are the default index structure in PostgreSQL. Internally, they are also called B+ tree, as described by Lehman-Yao. On this box (AMD, 4 Ghz), we can build the B-tree index in around 4 seconds, without any database side tweaks. Once the index is in place, the SELECT command will be executed at lightning speed: test=# SELECT * FROM t_test WHERE id = 423423;   id   | name--------+------423423 | hans(1 row)Time: 0.384 ms The query executes in less than a millisecond. Keep in mind that this already includes displaying the data, and the query is a lot faster internally. Analyzing the performance of a query How do we know that the query is actually a lot faster? In the previous section, you saw EXPLAIN in action already. However, there is a little more to know about this command. You can add some instructions to EXPLAIN to make it a lot more verbose, as shown here: test=# h EXPLAINCommand:     EXPLAINDescription: show the execution plan of a statementSyntax:EXPLAIN [ ( option [, ...] ) ] statementEXPLAIN [ ANALYZE ] [ VERBOSE ] statement In the preceding code, the term option can be one of the following:    ANALYZE [ boolean ]   VERBOSE [ boolean ]   COSTS [ boolean ]   BUFFERS [ boolean ]   TIMING [ boolean ]   FORMAT { TEXT | XML | JSON | YAML } Consider the following example: test=# EXPLAIN (ANALYZE TRUE, VERBOSE true, COSTS TRUE,   TIMING true) SELECT * FROM t_test WHERE id = 423423;               QUERY PLAN        ------------------------------------------------------Index Scan using idx_id on public.t_test(cost=0.43..8.45 rows=1 width=9)(actual time=0.016..0.018 rows=1 loops=1)   Output: id, name   Index Cond: (t_test.id = 423423)Total runtime: 0.042 ms(4 rows)Time: 0.536 ms The ANALYZE function does a special form of execution. It is a good way to figure out which part of the query burned most of the time. Again, we can read things inside out. In addition to the estimated costs of the query, we can also see the real execution time. In our case, the index scan takes 0.018 milliseconds. Fast, isn't it? Given these timings, you can see that displaying the result actually takes a huge fraction of the time. The beauty of EXPLAIN ANALYZE is that it shows costs and execution times for every step of the process. This is important for you to familiarize yourself with this kind of output because when a programmer hits your desk complaining about bad performance, it is necessary to dig into this kind of stuff quickly. In many cases, the secret to performance is hidden in the execution plan, revealing a missing index or so. It is recommended to pay special attention to situations where the number of expected rows seriously differs from the number of rows really processed. Keep in mind that the planner is usually right, but not always. Be cautious in case of large differences (especially if this input is fed into a nested loop). Whenever a query feels slow, we always recommend to take a look at the plan first. In many cases, you will find missing indexes. The internal structure of a B-tree index Before we dig further into the B-tree indexes, we can briefly discuss what an index actually looks like under the hood. Understanding the B-tree internals Consider the following image that shows how things work: In PostgreSQL, we use the so-called Lehman-Yao B-trees (check out http://www.cs.cmu.edu/~dga/15-712/F07/papers/Lehman81.pdf). The main advantage of the B-trees is that they can handle concurrency very nicely. It is possible that hundreds or thousands of concurrent users modify the tree at the same time. Unfortunately, there is not enough room in this book to explain precisely how this works. The two most important issues of this tree are the facts that I/O is done in 8,000 chunks and that the tree is actually a sorted structure. This allows PostgreSQL to apply a ton of optimizations. Providing a sorted order As we stated before, a B-tree provides the system with sorted output. This can come in quite handy. Here is a simple query to make use of the fact that a B-tree provides the system with sorted output: test=# explain SELECT * FROM t_test ORDER BY id LIMIT 3;                   QUERY PLAN                                    ------------------------------------------------------Limit (cost=0.43..0.67 rows=3 width=9)   -> Index Scan using idx_id on t_test(cost=0.43..320094.43 rows=4000000 width=9)(2 rows) In this case, we are looking for the three smallest values. PostgreSQL will read the index from left to right and stop as soon as enough rows have been returned. This is a very common scenario. Many people think that indexes are only about searching, but this is not true. B-trees are also present to help out with sorting. Why do you, the DBA, care about this stuff? Remember that this is a typical use case where a software developer comes to your desk, pounds on the table, and complains. A simple index can fix the problem. Combined indexes Combined indexes are one more source of trouble if they are not used properly. A combined index is an index covering more than one column. Let's drop the existing index and create a combined index (make sure your seq_page_cost variable is set back to default to make the following examples work): test=# DROP INDEX idx_combined;DROP INDEXtest=# CREATE INDEX idx_combined ON t_test (name, id);CREATE INDEX We defined a composite index consisting of two columns. Remember that we put the name before the ID. A simple query will return the following execution plan: test=# explain analyze SELECT * FROM t_test   WHERE id = 10;               QUERY PLAN                                              -------------------------------------------------Seq Scan on t_test (cost=0.00..71622.00 rows=1   width=9)(actual time=181.502..351.439 rows=1 loops=1)   Filter: (id = 10)   Rows Removed by Filter: 3999999Total runtime: 351.481 ms(4 rows) There is no proper index for this, so the system will fall back to a sequential scan. Why is there no proper index? Well, try to look up for first names only in the telephone book. This is not going to work because a telephone book is sorted by location, last name, and first name. The same applies to our index. A B-tree works basically on the same principles as an ordinary paper phone book. It is only useful if you look up the first couple of values, or simply all of them. Here is an example: test=# explain analyze SELECT * FROM t_test   WHERE id = 10 AND name = 'joe';     QUERY PLAN                                                    ------------------------------------------------------Index Only Scan using idx_combined on t_test   (cost=0.43..6.20 rows=1 width=9)(actual time=0.068..0.068 rows=0 loops=1)   Index Cond: ((name = 'joe'::text) AND (id = 10))   Heap Fetches: 0Total runtime: 0.108 ms(4 rows) In this case, the combined index comes up with a high speed result of 0.1 ms, which is not bad. After this small example, we can turn to an issue that's a little bit more complex. Let's change the costs of a sequential scan to 100-times normal: test=# SET seq_page_cost TO 100;SET Don't let yourself be fooled into believing that an index is always good: test=# explain analyze SELECT * FROM t_testWHERE id = 10;                   QUERY PLAN                ------------------------------------------------------Index Only Scan using idx_combined on t_test(cost=0.43..91620.44 rows=1 width=9)(actual time=0.362..177.952 rows=1 loops=1)   Index Cond: (id = 10)   Heap Fetches: 1Total runtime: 177.983 ms(4 rows) Just look at the execution times. We are almost as slow as a sequential scan here. Why does PostgreSQL use the index at all? Well, let's assume we have a very broad table. In this case, sequentially scanning the table is expensive. Even if we have to read the entire index, it can be cheaper than having to read the entire table, at least if there is enough hope to reduce the amount of data by using the index somehow. So, in case you see an index scan, also take a look at the execution times and the number of rows used. The index might not be perfect, but it's just an attempt by PostgreSQL to avoid the worse to come. Keep in mind that there is no general rule (for example, more than 25 percent of data will result in a sequential scan) for sequential scans. The plans depend on a couple of internal issues, such as physical disk layout (correlation) and so on. Partial indexes Up to now, an index covered the entire table. This is not always necessarily the case. There are also partial indexes. When is a partial index useful? Consider the following example: test=# CREATE TABLE t_invoice (   id     serial,   d     date,   amount   numeric,   paid     boolean);CREATE TABLEtest=# CREATE INDEX idx_partial   ON   t_invoice (paid)   WHERE   paid = false;CREATE INDEX In our case, we create a table storing invoices. We can safely assume that the majority of the invoices are nicely paid. However, we expect a minority to be pending, so we want to search for them. A partial index will do the job in a highly space efficient way. Space is important because saving on space has a couple of nice side effects, such as cache efficiency and so on. Dealing with different types of indexes Let's move on to an important issue: not everything can be sorted easily and in a useful way. Have you ever tried to sort circles? If the question seems odd, just try to do it. It will not be easy and will be highly controversial, so how do we do it best? Would we sort by size or coordinates? Under any circumstances, using a B-tree to store circles, points, or polygons might not be a good idea at all. A B-tree does not do what you want it to do because a B-tree depends on some kind of sorting order. To provide end users with maximum flexibility and power, PostgreSQL provides more than just one index type. Each index type supports certain algorithms used for different purposes. The following list of index types is available in PostgreSQL (as of Version 9.4.1): btree: These are the high-concurrency B-trees gist: This is an index type for geometric searches (GIS data) and for KNN-search gin: This is an index type optimized for Full-Text Search (FTS) sp-gist: This is a space-partitioned gist As we mentioned before, each type of index serves different purposes. We highly encourage you to dig into this extremely important topic to make sure that you can help software developers whenever necessary. Unfortunately, we don't have enough room in this book to discuss all the index types in greater depth. If you are interested in finding out more, we recommend checking out information on my website at http://www.postgresql-support.de/slides/2013_dublin_indexing.pdf. Alternatively, you can look up the official PostgreSQL documentation, which can be found at http://www.postgresql.org/docs/9.4/static/indexes.html. Detecting missing indexes Now that we have covered the basics and some selected advanced topics of indexing, we want to shift our attention to a major and highly important administrative task: hunting down missing indexes. When talking about missing indexes, there is one essential query I have found to be highly valuable. The query is given as follows: test=# xExpanded display (expanded) is on.test=# SELECT   relname, seq_scan, seq_tup_read,     idx_scan, idx_tup_fetch,     seq_tup_read / seq_scanFROM   pg_stat_user_tablesWHERE   seq_scan > 0ORDER BY seq_tup_read DESC;-[ RECORD 1 ]-+---------relname       | t_user  seq_scan     | 824350      seq_tup_read | 2970269443530idx_scan     | 0      idx_tup_fetch | 0      ?column?     | 3603165 The pg_stat_user_tables option contains statistical information about tables and their access patterns. In this example, we found a classic problem. The t_user table has been scanned close to 1 million times. During these sequential scans, we processed close to 3 trillion rows. Do you think this is unusual? It's not nearly as unusual as you might think. In the last column, we divided seq_tup_read through seq_scan. Basically, this is a simple way to figure out how many rows a typical sequential scan has used to finish. In our case, 3.6 million rows had to be read. Do you remember our initial example? We managed to read 4 million rows in a couple of hundred milliseconds. So, it is absolutely realistic that nobody noticed the performance bottleneck before. However, just consider burning, say, 300 ms for every query thousands of times. This can easily create a heavy load on a totally unnecessary scale. In fact, a missing index is the key factor when it comes to bad performance. Let's take a look at the table description now: test=# d t_user                         Table "public.t_user"Column | Type   |       Modifiers                    ----------+---------+-------------------------------id      | integer | not null default               nextval('t_user_id_seq'::regclass)email   | text   |passwd   | text   |Indexes:   "t_user_pkey" PRIMARY KEY, btree (id) This is really a classic example. It is hard to tell how often I have seen this kind of example in the field. The table was probably called customer or userbase. The basic principle of the problem was always the same: we got an index on the primary key, but the primary key was never checked during the authentication process. When you log in to Facebook, Amazon, Google, and so on, you will not use your internal ID, you will rather use your e-mail address. Therefore, it should be indexed. The rules here are simple: we are searching for queries that needed many expensive scans. We don't mind sequential scans as long as they only read a handful of rows or as long as they show up rarely (caused by backups, for example). We need to keep expensive scans in mind, however ("expensive" in terms of "many rows needed"). Here is an example code snippet that should not bother us at all: -[ RECORD 1 ]-+---------relname       | t_province  seq_scan     | 8345345      seq_tup_read | 100144140idx_scan     | 0      idx_tup_fetch | 0      ?column?     | 12 The table has been read 8 million times, but in an average, only 12 rows have been returned. Even if we have 1 million indexes defined, PostgreSQL will not use them because the table is simply too small. It is pretty hard to tell which columns might need an index from inside PostgreSQL. However, taking a look at the tables and thinking about them for a minute will, in most cases, solve the riddle. In many cases, things are pretty obvious anyway, and developers will be able to provide you with a reasonable answer. As you can see, finding missing indexes is not hard, and we strongly recommend checking this system table once in a while to figure out whether your system works nicely. There are a couple of tools, such as pgbadger, out there that can help us to monitor systems. It is recommended that you make use of such tools. There is not only light, there is also some shadow. Indexes are not always good. They can also cause considerable overhead during writes. Keep in mind that when you insert, modify, or delete data, you have to touch the indexes as well. The overhead of useless indexes should never be underestimated. Therefore, it makes sense to not just look for missing indexes, but also for spare indexes that don't serve a purpose anymore. Detecting slow queries Now that we have seen how to hunt down tables that might need an index, we can move on to the next example and try to figure out the queries that cause most of the load on your system. Sometimes, the slowest query is not the one causing a problem; it is a bunch of small queries, which are executed over and over again. In this section, you will learn how to track down such queries. To track down slow operations, we can rely on a module called pg_stat_statements. This module is available as part of the PostgreSQL contrib section. Installing a module from this section is really easy. Connect to PostgreSQL as a superuser, and execute the following instruction (if contrib packages have been installed): test=# CREATE EXTENSION pg_stat_statements;CREATE EXTENSION This module will install a system view that will contain all the relevant information we need to find expensive operations: test=# d pg_stat_statements         View "public.pg_stat_statements"       Column       |       Type       | Modifiers---------------------+------------------+-----------userid             | oid             |dbid               | oid             |queryid             | bigint          |query               | text             |calls               | bigint           |total_time         | double precision |rows               | bigint           |shared_blks_hit     | bigint           |shared_blks_read   | bigint           |shared_blks_dirtied | bigint           |shared_blks_written | bigint           |local_blks_hit     | bigint           |local_blks_read     | bigint           |local_blks_dirtied | bigint           |local_blks_written | bigint           |temp_blks_read     | bigint           |temp_blks_written   | bigint           |blk_read_time       | double precision |blk_write_time     | double precision | In this view, we can see the queries we are interested in, the total execution time (total_time), the number of calls, and the number of rows returned. Then, we will get some information about the I/O behavior (more on caching later) of the query as well as information about temporary data being read and written. Finally, the last two columns will tell us how much time we actually spent on I/O. The final two fields are active when track_timing in postgresql.conf has been enabled and will give vital insights into potential reasons for disk wait and disk-related speed problems. The blk_* prefix will tell us how much time a certain query has spent reading and writing to the operating system. Let's see what happens when we want to query the view: test=# SELECT * FROM pg_stat_statements;ERROR: pg_stat_statements must be loaded via   shared_preload_libraries The system will tell us that we have to enable this module; otherwise, data won't be collected. All we have to do to make this work is to add the following line to postgresql.conf: shared_preload_libraries = 'pg_stat_statements' Then, we have to restart the server to enable it. We highly recommend adding this module to the configuration straightaway to make sure that a restart can be avoided and that this data is always around. Don't worry too much about the performance overhead of this module. Tests have shown that the impact on performance is so low that it is even too hard to measure. Therefore, it might be a good idea to have this module activated all the time. If you have configured things properly, finding the most time-consuming queries should be simple: SELECT *FROM   pg_stat_statementsORDER   BY total_time DESC; The important part here is that PostgreSQL can nicely group queries. For instance: SELECT * FROM foo WHERE bar = 1;SELECT * FROM foo WHERE bar = 2; PostgreSQL will detect that this is just one type of query and replace the two numbers in the WHERE clause with a placeholder indicating that a parameter was used here. Of course, you can also sort by any other criteria: highest I/O time, highest number of calls, or whatever. The pg_stat_statement function has it all, and things are available in a way that makes the data very easy and efficient to use. How to reset statistics Sometimes, it is necessary to reset the statistics. If you are about to track down a problem, resetting can be very beneficial. Here is how it works: test=# SELECT pg_stat_reset();pg_stat_reset---------------(1 row)test=# SELECT pg_stat_statements_reset();pg_stat_statements_reset--------------------------(1 row) The pg_stat_reset command will reset the entire system statistics (for example, pg_stat_user_tables). The second call will wipe out pg_stat_statements. Adjusting memory parameters After we find the slow queries, we can do something about them. The first step is always to fix indexing and make sure that sane requests are sent to the database. If you are requesting stupid things from PostgreSQL, you can expect trouble. Once the basic steps have been performed, we can move on to the PostgreSQL memory parameters, which need some tuning. Optimizing shared buffers One of the most essential memory parameters is shared_buffers. What are shared buffers? Let's assume we are about to read a table consisting of 8,000 blocks. PostgreSQL will check if the buffer is already in cache (shared_buffers), and if it is not, it will ask the underlying operating system to provide the database with the missing 8,000 blocks. If we are lucky, the operating system has a cached copy of the block. If we are not so lucky, the operating system has to go to the disk system and fetch the data (worst case). So, the more data we have in cache, the more efficient we will be. Setting shared_buffers to the right value is more art than science. The general guideline is that shared_buffers should consume 25 percent of memory, but not more than 16 GB. Very large shared buffer settings are known to cause suboptimal performance in some cases. It is also not recommended to starve the filesystem cache too much on behalf of the database system. Mentioning the guidelines does not mean that it is eternal law—you really have to see this as a guideline you can use to get started. Different settings might be better for your workload. Remember, if there was an eternal law, there would be no setting, but some autotuning magic. However, a contrib module called pg_buffercache can give some insights into what is in cache at the moment. It can be used as a basis to get started on understanding what is going on inside the PostgreSQL shared buffer. Changing shared_buffers can be done in postgresql.conf, shown as follows: shared_buffers = 4GB In our example, shared buffers have been set to 4GB. A database restart is needed to activate the new value. In PostgreSQL 9.4, some changes were introduced. Traditionally, PostgreSQL used a classical System V shared memory to handle the shared buffers. Starting with PostgreSQL 9.3, mapped memory was added, and finally, it was in PostgreSQL 9.4 that a config variable was introduced to configure the memory technique PostgreSQL will use, shown as follows: dynamic_shared_memory_type = posix# the default is the first option     # supported by the operating system:     #   posix     #   sysv     #   windows     #   mmap     # use none to disable dynamic shared memory The default value on the most common operating systems is basically fine. However, feel free to experiment with the settings and see what happens performance wise. Considering huge pages When a process uses RAM, the CPU marks this memory as used by this process. For efficiency reasons, the CPU usually allocates RAM by chunks of 4,000 bytes. These chunks are called pages. The process address space is virtual, and the CPU and operating system have to remember which process belongs to which page. The more pages you have, the more time it takes to find where the memory is mapped. When a process uses 1 GB of memory, it means that 262.144 blocks have to be looked up. Most modern CPU architectures support bigger pages, and these pages are called huge pages (on Linux). To tell PostgreSQL that this mechanism can be used, the following config variable can be changed in postgresql.conf: huge_pages = try                     # on, off, or try Of course, your Linux system has to know about the use of huge pages. Therefore, you can do some tweaking, as follows: grep Hugepagesize /proc/meminfoHugepagesize:     2048 kB In our case, the size of the huge pages is 2 MB. So, if there is 1 GB of memory, 512 huge pages are needed. The number of huge pages can be configured and activated by setting nr_hugepages in the proc filesystem. Consider the following example: echo 512 > /proc/sys/vm/nr_hugepages Alternatively, we can use the sysctl command or change things in /etc/sysctl.conf: sysctl -w vm.nr_hugepages=512 Huge pages can have a significant impact on performance. Tweaking work_mem There is more to PostgreSQL memory configuration than just shared buffers. The work_mem parameter is widely used for operations such as sorting, aggregating, and so on. Let's illustrate the way work_mem works with a short, easy-to-understand example. Let's assume it is an election day and three parties have taken part in the elections. The data is as follows: test=# CREATE TABLE t_election (id serial, party text);test=# INSERT INTO t_election (party)SELECT 'socialists'   FROM generate_series(1, 439784);test=# INSERT INTO t_election (party)SELECT 'conservatives'   FROM generate_series(1, 802132);test=# INSERT INTO t_election (party)SELECT 'liberals'   FROM generate_series(1, 654033); We add some data to the table and try to count how many votes each party has: test=# explain analyze SELECT party, count(*)   FROM   t_election   GROUP BY 1;       QUERY PLAN                                                        ------------------------------------------------------HashAggregate (cost=39461.24..39461.26 rows=3     width=11) (actual time=609.456..609.456   rows=3 loops=1)     Group Key: party   -> Seq Scan on t_election (cost=0.00..29981.49     rows=1895949 width=11)   (actual time=0.007..192.934 rows=1895949   loops=1)Planning time: 0.058 msExecution time: 609.481 ms(5 rows) First of all, the system will perform a sequential scan and read all the data. This data is passed on to a so-called HashAggregate. For each party, PostgreSQL will calculate a hash key and increment counters as the query moves through the tables. At the end of the operation, we will have a chunk of memory with three values and three counters. Very nice! As you can see, the explain analyze statement does not take more than 600 ms. Note that the real execution time of the query will be a lot faster. The explain analyze statement does have some serious overhead. Still, it will give you valuable insights into the inner workings of the query. Let's try to repeat this same example, but this time, we want to group by the ID. Here is the execution plan: test=# explain analyze SELECT id, count(*)   FROM   t_election     GROUP BY 1;       QUERY PLAN                                                          ------------------------------------------------------GroupAggregate (cost=253601.23..286780.33 rows=1895949     width=4) (actual time=1073.769..1811.619     rows=1895949 loops=1)     Group Key: id   -> Sort (cost=253601.23..258341.10 rows=1895949   width=4) (actual time=1073.763..1288.432   rows=1895949 loops=1)         Sort Key: id       Sort Method: external sort Disk: 25960kB         -> Seq Scan on t_election         (cost=0.00..29981.49 rows=1895949 width=4)     (actual time=0.013..235.046 rows=1895949     loops=1)Planning time: 0.086 msExecution time: 1928.573 ms(8 rows) The execution time rises by almost 2 seconds and, more importantly, the plan changes. In this scenario, there is no way to stuff all the 1.9 million hash keys into a chunk of memory because we are limited by work_mem. Therefore, PostgreSQL has to find an alternative plan. It will sort the data and run GroupAggregate. How does it work? If you have a sorted list of data, you can count all equal values, send them off to the client, and move on to the next value. The main advantage is that we don't have to keep the entire result set in memory at once. With GroupAggregate, we can basically return aggregations of infinite sizes. The downside is that large aggregates exceeding memory will create temporary files leading to potential disk I/O. Keep in mind that we are talking about the size of the result set and not about the size of the underlying data. Let's try the same thing with more work_mem: test=# SET work_mem TO '1 GB';SETtest=# explain analyze SELECT id, count(*)   FROM t_election   GROUP BY 1;         QUERY PLAN                                                        ------------------------------------------------------HashAggregate (cost=39461.24..58420.73 rows=1895949     width=4) (actual time=857.554..1343.375   rows=1895949 loops=1)   Group Key: id   -> Seq Scan on t_election (cost=0.00..29981.49   rows=1895949 width=4)   (actual time=0.010..201.012   rows=1895949 loops=1)Planning time: 0.113 msExecution time: 1478.820 ms(5 rows) In this case, we adapted work_mem for the current session. Don't worry, changing work_mem locally does not change the parameter for other database connections. If you want to change things globally, you have to do so by changing things in postgresql.conf. Alternatively, 9.4 offers a command called ALTER SYSTEM SET work_mem TO '1 GB'. Once SELECT pg_reload_conf() has been called, the config parameter is changed as well. What you see in this example is that the execution time is around half a second lower than before. PostgreSQL switches back to the more efficient plan. However, there is more; work_mem is also in charge of efficient sorting: test=# explain analyze SELECT * FROM t_election ORDER BY id DESC;     QUERY PLAN                                                          ------------------------------------------------------Sort (cost=227676.73..232416.60 rows=1895949 width=15)   (actual time=695.004..872.698 rows=1895949   loops=1)   Sort Key: id   Sort Method: quicksort Memory: 163092kB   -> Seq Scan on t_election (cost=0.00..29981.49   rows=1895949 width=15) (actual time=0.013..188.876rows=1895949 loops=1)Planning time: 0.042 msExecution time: 995.327 ms(6 rows) In our example, PostgreSQL can sort the entire dataset in memory. Earlier, we had to perform a so-called "external sort Disk", which is way slower because temporary results have to be written to disk. The work_mem command is used for some other operations as well. However, sorting and aggregation are the most common use cases. Keep in mind that work_mem should not be abused, and work_mem can be allocated to every sorting or grouping operation. So, more than just one work_mem amount of memory might be allocated by a single query. Improving maintenance_work_mem To control the memory consumption of administrative tasks, PostgreSQL offers a parameter called maintenance_work_mem. It is used to handle index creations as well as VACUUM. Usually, creating an index (B-tree) is mostly related to sorting, and the idea of maintenance_work_mem is to speed things up. However, things are not as simple as they might seem. People might assume that increasing the parameter will always speed things up, but this is not necessarily true; in fact, smaller values might even be beneficial. We conducted some research to solve this riddle. The in-depth results of this research can be found at http://www.cybertec.at/adjusting-maintenance_work_mem/. However, indexes are not the only beneficiaries. The maintenance_work_mem command is also here to help VACUUM clean out indexes. If maintenance_work_mem is too low, you might see VACUUM scanning tables repeatedly because dead items cannot be stored in memory during VACUUM. This is something that should basically be avoided. Just like all other memory parameters, maintenance_work_mem can be set per session, or it can be set globally in postgresql.conf. Adjusting effective_cache_size The number of shared_buffers assigned to PostgreSQL is not the only cache in the system. The operating system will also cache data and do a great job of improving speed. To make sure that the PostgreSQL optimizer knows what to expect from the operation system, effective_cache_size has been introduced. The idea is to tell PostgreSQL how much cache there is going to be around (shared buffers + operating system side cache). The optimizer can then adjust its costs and estimates to reflect this knowledge. It is recommended to always set this parameter; otherwise, the planner might come up with suboptimal plans. Summary In this article, you learned how to detect basic performance bottlenecks. In addition to this, we covered the very basics of the PostgreSQL optimizer and indexes. At the end of the article, some important memory parameters were presented. Resources for Article: Further resources on this subject: PostgreSQL 9: Reliable Controller and Disk Setup [article] Running a PostgreSQL Database Server [article] PostgreSQL: Tips and Tricks [article]
Read more
  • 0
  • 0
  • 3541

article-image-web-api-and-client-integration
Packt
10 Oct 2014
9 min read
Save for later

Web API and Client Integration

Packt
10 Oct 2014
9 min read
In this article written by Geoff Webber-Cross, the author of Learning Microsoft Azure, we'll create an on-premise production management client Windows application allowing manufacturing staff to view and update order and batch data and a web service to access data in the production SQL database and send order updates to the Service Bus topic. (For more resources related to this topic, see here.) The site's main feature is an ASP.NET Web API 2 HTTP service that allows the clients to read order and batch data. The site will also host a SignalR (http://signalr.net/) hub that allows the client to update order and batch statuses and have the changes broadcast to all the on-premise clients to keep them synchronized in real time. Both the Web API and SignalR hubs will use the Azure Active Directory authentication. We'll cover the following topic in this article: Building a client application Building a client application For the client application, we'll create a WPF client application to display batches and orders and allow us to change their state. We'll use MVVM Light again, like we did for the message simulator we created in the sales solution, to help us implement a neat MVVM pattern. We'll create a number of data services to get data from the API using Azure AD authentication. Preparing the WPF project We'll create a WPF application and install NuGet packages for MVVM Light, JSON.NET, and Azure AD authentication in the following procedure (for the Express version of Visual Studio, you'll need Visual Studio Express for desktops): Add a WPF project to the solution called ManagementApplication. In the NuGet Package Manager Console, enter the following command to install MVVM Light: install-package mvvmlight Now, enter the following command to install the Microsoft.IdentityModel.Clients.ActiveDirectory package: install-package Microsoft.IdentityModel.Clients.ActiveDirectory Now, enter the following command to install JSON.NET: install-package newtonsoft.json Enter the following command to install the SignalR client package (note that this is different from the server package): Install-package Microsoft.AspNet.SignalR.Client Add a project reference to ProductionModel by right-clicking on the References folder and selecting Add Reference, check ProductionModel by navigating to the Solution | Projects tab, and click on OK. Add a project reference to System.Configuraton and System.Net.Http by right-clicking on the References folder and selecting Add Reference, check System.Config and System.Net.Http navigating to the Assemblies | Framework tab, and click on OK. In the project's Settings.settings file, add a string setting called Token to store the user's auth token. Add the following appSettings block to App.config; I've put comments to help you understand (and remember) what they stand for and added commented-out settings for the Azure API: <appSettings> <!-- AD Tenant --> <add key="ida:Tenant" value="azurebakery.onmicrosoft.com" />    <!-- The target api AD application APP ID (get it from    config tab in portal) --> <!-- Local --> <add key="ida:Audience"    value="https://azurebakery.onmicrosoft.com/ManagementWebApi" /> <!-- Azure --> <!-- <add key="ida:Audience"    value="https://azurebakery.onmicrosoft.com/      WebApp-azurebakeryproduction.azurewebsites.net" /> -->    <!-- The client id of THIS application (get it from    config tab in portal) --> <add key="ida:ClientID" value=    "1a1867d4-9972-45bb-a9b8-486f03ad77e9" />    <!-- Callback URI for OAuth workflow --> <add key="ida:CallbackUri"    value="https://azurebakery.com" />    <!-- The URI of the Web API --> <!-- Local --> <add key="serviceUri" value="https://localhost:44303/" /> <!-- Azure --> <!-- <add key="serviceUri" value="https://azurebakeryproduction.azurewebsites.net/" /> --> </appSettings> Add the MVVM Light ViewModelLocator to Application.Resources in App.xaml: <Application.Resources>    <vm:ViewModelLocator x_Key="Locator"      d_IsDataSource="True"                      DataContext="{Binding Source={StaticResource          Locator}, Path=Main}"        Title="Production Management Application"          Height="350" Width="525"> Creating an authentication base class Since the Web API and SignalR hubs use Azure AD authentication, we'll create services to interact with both and create a common base class to ensure that all requests are authenticated. This class uses the AuthenticationContext.AquireToken method to launch a built-in login dialog that handles the OAuth2 workflow and returns an authentication token on successful login: using Microsoft.IdentityModel.Clients.ActiveDirectory; using System; using System.Configuration; using System.Diagnostics; using System.Net;   namespace AzureBakery.Production.ManagementApplication.Services {    public abstract class AzureAdAuthBase    {        protected AuthenticationResult Token = null;          protected readonly string ServiceUri = null;          protected AzureAdAuthBase()        {            this.ServiceUri =              ConfigurationManager.AppSettings["serviceUri"]; #if DEBUG            // This will accept temp SSL certificates            ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => true; #endif        }          protected bool Login()        {            // Our AD Tenant domain name            var tenantId =              ConfigurationManager.AppSettings["ida:Tenant"];              // Web API resource ID (The resource we want to use)            var resourceId =              ConfigurationManager.AppSettings["ida:Audience"];              // Client App CLIENT ID (The ID of the AD app for this            client application)            var clientId =              ConfigurationManager.AppSettings["ida:ClientID"];              // Callback URI            var callback = new              Uri(ConfigurationManager.AppSettings["ida:CallbackUri"]);              var authContext = new              AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantId));              if(this.Token == null)            {                // See if we have a cached token               var token = Properties.Settings.Default.Token;                if (!string.IsNullOrWhiteSpace(token))                    this.Token = AuthenticationResult.Deserialize(token);            }                       if (this.Token == null)             {                try                {                    // Acquire fresh token - this will get user to                    login                                  this.Token =                      authContext.AcquireToken(resourceId,                         clientId, callback);                }                catch(Exception ex)                {                    Debug.WriteLine(ex.ToString());                      return false;                }            }            else if(this.Token.ExpiresOn < DateTime.UtcNow)            {                // Refresh existing token this will not require                login                this.Token =                  authContext.AcquireTokenByRefreshToken(this.Token.RefreshToken,                   clientId);            }                      if (this.Token != null && this.Token.ExpiresOn >              DateTime.UtcNow)            {                // Store token                Properties.Settings.Default.Token =                  this.Token.Serialize(); // This should be                    encrypted                Properties.Settings.Default.Save();                  return true;            }              // Clear token            this.Token = null;              Properties.Settings.Default.Token = null;            Properties.Settings.Default.Save();              return false;        }    } } The token is stored in user settings and refreshed if necessary, so the users don't have to log in to the application every time they use it. The Login method can be called by derived service classes every time a service is called to check whether the user is logged in and whether there is a valid token to use. Creating a data service We'll create a DataService class that derives from the AzureAdAuthBase class we just created and gets data from the Web API service using AD authentication. First, we'll create a generic helper method that calls an API GET action using the HttpClient class with the authentication token added to the Authorization header, and deserializes the returned JSON object into a .NET-typed object T: private async Task<T> GetData<T>(string action) {    if (!base.Login())        return default(T);      // Call Web API    var authHeader = this.Token.CreateAuthorizationHeader();    var client = new HttpClient();    var uri = string.Format("{0}{1}", this.ServiceUri,      string.Format("api/{0}", action));    var request = new HttpRequestMessage(HttpMethod.Get, uri);    request.Headers.TryAddWithoutValidation("Authorization",      authHeader);      // Get response    var response = await client.SendAsync(request);    var responseString = await response.Content.ReadAsStringAsync();      // Deserialize JSON    var data = await Task.Factory.StartNew(() =>      JsonConvert.DeserializeObject<T>(responseString));      return data; } Once we have this, we can quickly create methods for getting order and batch data like this:   public async Task<IEnumerable<Order>> GetOrders() {    return await this.GetData<IEnumerable<Order>>("orders"); }   public async Task<IEnumerable<Batch>> GetBatches() {    return await this.GetData<IEnumerable<Batch>>("batches"); } This service implements an IDataService interface and is registered in the ViewModelLocator class, ready to be injected into our view models like this: SimpleIoc.Default.Register<IDataService, DataService>(); Creating a SignalR service We'll create another service derived from the AzureAdAuthBase class, which is called ManagementService, and which sends updated orders to the SignalR hub and receives updates from the hub originating from other clients to keep the UI updated in real time. First, we'll create a Register method, which creates a hub proxy using our authorization token from the base class, registers for updates from the hub, and starts the connection: private IHubProxy _proxy = null;   public event EventHandler<Order> OrderUpdated; public event EventHandler<Batch> BatchUpdated;   public ManagementService() {   }   public async Task Register() {    // Login using AD OAuth    if (!this.Login())        return;      // Get header from auth token    var authHeader = this.Token.CreateAuthorizationHeader();      // Create hub proxy and add auth token    var cnString = string.Format("{0}signalr", base.ServiceUri);    var hubConnection = new HubConnection(cnString, useDefaultUrl:      false);    this._proxy = hubConnection.CreateHubProxy("managementHub");    hubConnection.Headers.Add("Authorization", authHeader);      // Register for order updates    this._proxy.On<Order>("updateOrder", order =>    {        this.OnOrderUpdated(order);    });        // Register for batch updates    this._proxy.On<Batch>("updateBatch", batch =>    {        this.OnBatchUpdated(batch);    });        // Start hub connection    await hubConnection.Start(); } The OnOrderUpdated and OnBatchUpdated methods call events to notify about updates. Now, add two methods that call the hub methods we created in the website using the IHubProxy.Invoke<T> method: public async Task<bool> UpdateOrder(Order order) {    // Invoke updateOrder method on hub    await this._proxy.Invoke<Order>("updateOrder",      order).ContinueWith(task =>    {        return !task.IsFaulted;    });      return false; }   public async Task<bool> UpdateBatch(Batch batch) {    // Invoke updateBatch method on hub    await this._proxy.Invoke<Batch>("updateBatch",      batch).ContinueWith(task =>    {        return !task.IsFaulted;    });      return false; } This service implements an IManagementService interface and is registered in the ViewModelLocator class, ready to be injected into our view models like this: SimpleIoc.Default.Register<IManagementService, ManagementService>(); Testing the application To test the application locally, we need to start the Web API project and the WPF client application at the same time. So, under the Startup Project section in the Solution Properties dialog, check Multiple startup projects, select the two applications, and click on OK: Once running, we can easily debug both applications simultaneously. To test the application with the service running in the cloud, we need to deploy the service to the cloud, and then change the settings in the client app.config file (remember we put the local and Azure settings in the config with the Azure settings commented-out, so swap them around). To debug the client against the Azure service, make sure that only the client application is running (select Single startup project from the Solution Properties dialog). Summary We learned how to use a Web API to enable the production management Windows client application to access data from our production database and a SignalR hub to handle order and batch changes, keeping all clients updated and messaging the Service Bus topic. Resources for Article: Further resources on this subject: Using the Windows Azure Platform PowerShell Cmdlets [Article] Windows Azure Mobile Services - Implementing Push Notifications using [Article] Using Azure BizTalk Features [Article]
Read more
  • 0
  • 0
  • 3044

article-image-administering-and-monitoring-processes
Packt
09 Oct 2014
25 min read
Save for later

Administering and Monitoring Processes

Packt
09 Oct 2014
25 min read
In this article by Alexandre Borges, the author of Solaris 11 Advanced Administration, we will cover the following topics: Monitoring and handling process execution Managing processes' priority on Solaris 11 Configuring FSS and applying it to projects When working with Oracle Solaris 11, many of the executing processes compose applications, and even the operating system itself runs many other processes and threads, which takes care of the smooth working of the environment. So, administrators have a daily task of monitoring the entire system and taking some hard decisions, when necessary. Furthermore, not all processes have the same priority and urgency, and there are some situations where it is suitable to give higher priority to one process than another (for example, rendering images). Here, we introduce a key concept: scheduling classes. Oracle Solaris 11 has a default process scheduler (svc:/system/scheduler:default) that controls the allocation of the CPU for each process according to its scheduling class. There are six important scheduling classes, as follows: Time Sharing (TS): By default, all processes or threads (non-GUI) are assigned to this class, where the priority value is dynamic and adjustable according to the system load (-60 to 60). Additionally, the system scheduler switches a process/thread with a lower priority from a processor to another process/thread with higher priority. Interactive (IA): This class has the same behavior as the TS class (dynamic and with an adjustable priority value from -60 to 60), but the IA class is suitable for GUI processes/threads that have an associated window. Additionally, when the mouse focus is on a window, the bound process or thread receives an increase of 10 points of its priority. When the mouse focuses on a window, the bound process loses the same 10 points. Fixed (FX): This class has the same behavior as that of TS, except that any process or thread that is associated with this class has its priority value fixed. The value range is from 0 to 59, but the initial priority of the process or thread is kept from the beginning to end of the life process. System (SYS): This class is used for kernel processes or threads where the possible priority goes from 60 to 99. However, once the kernel process or thread begins processing, it's bound to the CPU until the end of its life (the system scheduler doesn't take it off the processor). Realtime (RT): Processes and threads from this class have a fixed priority that ranges from 100 to 159. Any process or thread of this class has a higher priority than any other class. Fair share scheduler (FSS): Any process or thread managed by this class is scheduled based on its share value (and not on its priority value) and in the processor's utilization. The priority range goes from -60 to 60. Usually, the FSS class is used when the administrator wants to control the resource distribution on the system using processor sets or when deploying Oracle zones. It is possible to change the priority and class of any process or thread (except the system class), but it is uncommon, such as using FSS. When handling a processor set (a group of processors), the processes bound to this group must belong to only one scheduling class (FSS or FX, but not both). It is recommended that you don't use the RT class unless it is necessary because RT processes are bound to the processor (or core) up to their conclusion, and it only allows any other process to execute when it is idle. The FSS class is based on shares, and personally, I establish a total of 100 shares and assign these shares to processes, threads, or even Oracle zones. This is a simple method to think about resources, such as CPUs, using percentages (for example, 10 shares = 10 percent). Monitoring and handling process execution Oracle Solaris 11 offers several methods to monitor and control process execution, and there isn't one best tool to do this because every technique has some advantages. Getting ready This recipe requires a virtual machine (VirtualBox or VMware) running Oracle Solaris 11 installed with a 2 GB RAM at least. It's recommended that the system has more than one processor or core. How to do it… A common way to monitor processes on Oracle Solaris 11 is using the old and good ps command: root@solaris11-1:~# ps -efcl -o s,uid,pid,zone,class,pri,vsz,rss,time,comm | more According to the output shown in the previous screenshot, we have: S (status) UID (user ID) PID (process ID) ZsONE (zone) CLS (scheduling class) PRI (priority) VSZ (virtual memory size) RSS (resident set size) TIME (the time for that the process runs on the CPU) COMMAND (the command used to start the process) Additionally, possible process statuses are as follows: O (running on a processor) S (sleeping—waiting for an event to complete) R (runnable—process is on a queue) T (process is stopped either because of a job control signal or because it is being traced) Z (zombie—process finished and parent is not waiting) W (waiting—process is waiting for the CPU usage to drop to the CPU-caps enforced limit) Do not get confused between the virtual memory size (VSZ) and resident set size (RSS). The VSZ of a process includes all information on a physical memory (RAM) plus all mapped files and devices (swap). On the other hand, the RSS value only includes the information in the memory (RAM). Other important command to monitor processes on Oracle Solaris 11 is the prstat tool. For example, it is possible to list the threads of each process by executing the following command: root@solaris11-1:~# prstat –L PID USERNAME SIZE   RSS STATE   PRI NICE     TIME CPU PROCESS/LWPID   2609 root     129M   18M sleep   15   0   0:00:24 1.1% gnome-terminal/1 1238 root       88M   74M sleep   59   0   0:00:41 0.5% Xorg/1 2549 root     217M   99M sleep     1   0   0:00:45 0.3% java/22 2549 root     217M   99M sleep     1   0   0:00:30 0.2% java/21 2581 root       13M 2160K sleep   59   0   0:00:24 0.2% VBoxClient/3 1840 root       37M 7660K sleep     1   0   0:00:26 0.2% pkg.depotd/2 (truncated output) The LWPID column shows the number of threads of each process. Other good options are –J (summary per project), -Z (summary per zone), and –mL (includes information about thread microstates). To collect some information about processes and projects, execute the following command: root@solaris11-1:~# prstat –J    PID USERNAME SIZE   RSS STATE   PRI NICE     TIME CPU PROCESS/NLWP     2549 root     217M   99M sleep   55   0   0:01:56 0.8% java/25 1238 root       88M   74M sleep   59   0   0:00:44 0.4% Xorg/3 1840 root       37M 7660K sleep     1   0   0:00:55 0.4% pkg.depotd/64 (truncated output) PROJID   NPROC SWAP   RSS MEMORY     TIME CPU PROJECT                          1       43 2264M 530M   13%   0:03:46 1.9% user.root                        0       79 844M 254M   6.1%   0:03:12 0.9% system                         3       2   11M 5544K   0.1%   0:00:55 0.0% default                     Total: 124 processes, 839 lwps, load averages: 0.23, 0.22, 0.22 Pay attention to the last column (PROJECT) from the second part of the output. It is very interesting to know that Oracle Solaris already works using projects and some of them are created by default. By the way, it is always appropriate to remember that the structure of a project is project | tasks | processes. Collecting information about processes and zones is done by executing the following command: root@solaris11-1:~# prstat -Z    PID USERNAME SIZE   RSS STATE   PRI NICE     TIME CPU PROCESS/NLWP     3735 root       13M   12M sleep   59   0   0:00:13 4.2% svc.configd/17 3733 root       17M 8676K sleep   59   0   0:00:05 2.0% svc.startd/15 2532 root     219M   83M sleep   47   0   0:00:15 0.8% java/25 1214 root      88M   74M sleep     1   0   0:00:09 0.6% Xorg/3    746 root       0K   0K sleep   99 -20   0:00:02 0.5% zpool-myzones/138 (truncated output) ZONEID   NPROC SWAP   RSS MEMORY     TIME CPU ZONE      1       11   92M   36M   0.9%   0:00:18 6.7% zone1      0     129 3222M 830M   20%   0:02:09 4.8% global      2       5   18M 6668K   0.2%   0:00:00 0.2% zone2 According to the output, there is a global zone and two other nonglobal zones (zone1 and zone2) in this system. Finally, to gather information about processes and their respective microstate information, execute the following command: root@solaris11-1:~# prstat –mL    PID USERNAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/LWPID 1925 pkg5srv 0.8 5.9 0.0 0.0 0.0 0.0 91 2.1 286   2 2K   0 htcacheclean/1 1214 root     1.6 3.4 0.0 0.0 0.0 0.0 92 2.7 279 24 3K   0 Xorg/1 2592 root     2.2 2.1 0.0 0.0 0.0 0.0 94 1.7 202   9 1K   0 gnome-termin/1 2532 root     0.9 1.4 0.0 0.0 0.0 97 0.0 1.2 202   4 304   0 java/22 5809 root     0.1 1.2 0.0 0.0 0.0 0.0 99 0.0 55   1 1K   0 prstat/1 2532 root     0.6 0.5 0.0 0.0 0.0 98 0.0 1.3 102   6 203   0 java/21 (truncated output) The output from prtstat –mL (gathering microstates information) is very interesting because it can give us some clues about performance problems. For example, the LAT column (latency) indicates the percentage of time wait for CPU (possible problems with the CPU) and, in this case, a constant value above zero could mean a CPU performance problem. Continuing the explanation, a possible problem with the memory can be highlighted using the TFL (the percentage of time the process has spent processing text page faults) and DFL columns (the percentage of time the process has spent processing data page faults), which shows whether and how many times (in percentage) a thread is waiting for memory paging. In a complementary manner, when handling processes, there are several useful commands, as shown in the following table: Objective Command To show the stack process pstack <pid> To kill a process pkill <process name> To get the process ID of a process pgrep –l <pid> To list the opened files by a process pfiles <pid> To get a memory map of a process pmap –x <pid> To list the shared libraries of a process pldd <pid> To show all the arguments of a process pargs –ea <pid> To trace a process truss –p <pid> To reap a zombie process preap <pid> For example, to find out which shared libraries are used by the top command, execute the following sequence of commands: root@solaris11-1:~# top root@solaris11-1:~# ps -efcl | grep top 0 S     root 2672 2649   IA 59       ?   1112       ? 05:32:53 pts/3       0:00 top 0 S     root 2674 2606   IA 54       ?   2149       ? 05:33:01 pts/2       0:00 grep top root@solaris11-1:~# pldd 2672 2672: top /lib/amd64/libc.so.1 /usr/lib/amd64/libkvm.so.1 /lib/amd64/libelf.so.1 /lib/amd64/libkstat.so.1 /lib/amd64/libm.so.2 /lib/amd64/libcurses.so.1 /lib/amd64/libthread.so.1 To find the top-most stack, execute the following command: root@solaris11-1:~# pstack 2672 2672: top ffff80ffbf54a66a pollsys (ffff80ffbfffd070, 1, ffff80ffbfffd1f0, 0) ffff80ffbf4f1995 pselect () + 181 ffff80ffbf4f1e14 select () + 68 000000000041a7d1 do_command () + ed 000000000041b5b3 main () + ab7 000000000040930c ???????? () To verify which files are opened by an application as the Firefox browser, we have to execute the following commands: root@solaris11-1:~# firefox & root@solaris11-1:~# ps -efcl | grep firefox 0 S     root 2600 2599  IA 59       ? 61589       ? 13:50:14 pts/1       0:07 firefox 0 S     root 2616 2601   IA 58       ?   2149       ? 13:51:18 pts/2       0:00 grep firefox root@solaris11-1:~# pfiles 2600 2600: firefox Current rlimit: 1024 file descriptors 0: S_IFCHR mode:0620 dev:563,0 ino:45703982 uid:0 gid:7 rdev:195,1      O_RDWR      /dev/pts/1      offset:997    1: S_IFCHR mode:0620 dev:563,0 ino:45703982 uid:0 gid:7 rdev:195,1      O_RDWR      /dev/pts/1      offset:997    2: S_IFCHR mode:0620 dev:563,0 ino:45703982 uid:0 gid:7 rdev:195,1      O_RDWR      /dev/pts/1      offset:997 (truncated output) Another excellent command from the previous table is pmap, which shows information about the address space of a process. For example, to see the address space of the current shell, execute the following command: root@solaris11-1:~# pmap -x $$ 2675: bash Address Kbytes     RSS   Anon Locked Mode   Mapped File 08050000   1208   1184       -       - r-x-- bash 0818E000     24     24       8       - rw--- bash 08194000     188     188     32       - rw---   [ heap ] EF470000     56     52       -       - r-x-- methods_unicode.so.3 EF48D000       8       8       -       - rwx-- methods_unicode.so.3 EF490000   6744     248       -     - r-x-- en_US.UTF-8.so.3 EFB36000       4       4       -       - rw--- en_US.UTF-8.so.3 FE550000     184     148       -       - r-x-- libcurses.so.1 FE58E000     16     16       -       - rw--- libcurses.so.1 FE592000       8       8       -     - rw--- libcurses.so.1 FE5A0000       4       4       4       - rw---   [ anon ] FE5B0000     24     24       -       - r-x-- libgen.so.1 FE5C6000       4       4       -       - rw--- libgen.so.1 FE5D0000     64     16       -       - rwx--   [ anon ] FE5EC000       4       4       -       - rwxs-   [ anon ] FE5F0000       4       4       4       - rw---   [ anon ] FE600000     24     12       4       - rwx--   [ anon ] FE610000   1352   1072       -       - r-x-- libc_hwcap1.so.1 FE772000     44     44     16       - rwx-- libc_hwcap1.so.1 FE77D000       4       4       -       - rwx-- libc_hwcap1.so.1 FE780000       4       4       4       - rw---   [ anon ] FE790000       4       4       4       - rw---   [ anon ] FE7A0000       4       4       -       - rw---   [ anon ] FE7A8000       4       4       -       - r--s-   [ anon ] FE7B4000     220     220       -       - r-x-- ld.so.1 FE7FB000       8       8       4       - rwx-- ld.so.1 FE7FD000       4       4       -       - rwx-- ld.so.1 FEFFB000     16     16       4       - rw---   [ stack ] -------- ------- ------- ------- ------- total Kb   10232   3332     84       - The pmap output shows us the following essential information: Address: This is the starting virtual address of each mapping Kbytes: This is the virtual size of each mapping RSS: The amount of RAM (in KB) for each mapping, including shared memory Anon: The number of pages of anonymous memory, which is usually and roughly defined as the sum of heap and stack pages without a counterpart on the disk (excluding the memory shared with other address spaces) Lock: The number of pages locked in the mapping Permissions: Virtual memory permissions for each mapping. The possible and valid permissions are as follows: x: Any instructions inside this mapping can be executed by the process w: The mapping can be written by the process r: The mapping can be read by the process s: The mapping is shared with other processes R: There is no swap space reserved for this process Mapped File: The name for each mapping such as an executable, a library, and anonymous pages (heap and stack) Finally, there is an excellent framework, DTrace, where you can get information on processes and anything else related to Oracle Solaris 11. What is DTrace? It is a clever instrumentation tool that is used for troubleshooting and, mainly, as a suitable framework for performance and analysis. DTrace is composed of thousands of probes (sensors) that are scattered through the Oracle Solaris kernel. To explain this briefly, when a program runs, any touched probe from memory, CPU, or I/O is triggered and gathers information from the related activity, giving us an insight on where the system is spending more time and making it possible to create reports. DTrace is nonintrusive (it does not add a performance burden on the system) and safe (by default, only the root user has enough privileges to use DTrace) and uses the Dscript language (similar to AWK). Different from other tools such as truss, apptrace, sar, prex, tnf, lockstat, and mdb, which allow knowing only the problematic area, DTrace provides the exact point of the problem. The fundamental structure of a DTrace probe is as follows: provider:module:function:name The previous probe is explained as follows: provider: These are libraries that instrument regions of the system, such as, syscall (system calls), proc (processes), fbt (function boundary tracing), lockstat, and so on module: This represents the shared library or kernel module where the probe was created function: This is a program, process, or thread function that contains the probe name: This is the probe's name When using DTrace, for each probe, it is possible to associate an action that will be executed if this probe is touched (triggered). By default, all probes are disabled and don't consume CPU processing. DTrace probes are listed by executing the following command: root@solaris11-1:~# dtrace -l | more The output of the previous command is shown in the following screenshot: The number of available probes on Oracle Solaris 11 are reported by the following command: root@solaris11-1:~# dtrace -l | wc –l    75899 After this brief introduction to DTrace, we can use it for listing any new processes (including their respective arguments) by running the following command: root@solaris11-1:~# dtrace -n 'proc:::exec-success { trace(curpsinfo->pr_psargs); }' dtrace: description 'proc:::exec-success ' matched 1 probe CPU     ID                   FUNCTION:NAME    3   7639         exec_common:exec-success   bash                                2   7639         exec_common:exec-success   /usr/bin/firefox                    0   7639         exec_common:exec-success   sh -c ps -e -o 'pid tty time comm'> /var/tmp/aaacLaiDl    0   7639         exec_common:exec-success   ps -e -o pid tty time comm          0   7639         exec_common:exec-success   ps -e -o pid tty time comm          1   7639         exec_common:exec-success   sh -c ps -e -o 'pid tty time comm'> /var/tmp/caaeLaiDl    2   7639        exec_common:exec-success   sh -c ps -e -o 'pid tty time comm'> /var/tmp/baadLaiDl    2   7639         exec_common:exec-success   ps -e -o pid tty (truncated output) There are very useful one-line tracers, as shown previously, available from Brendan Gregg's website at http://www.brendangregg.com/DTrace/dtrace_oneliners.txt. It is feasible to get any kind of information using DTrace. For example, get the system call count per program by executing the following command: root@solaris11-1:~# dtrace -n 'syscall:::entry { @num[pid,execname] = count(); }' dtrace: description 'syscall:::entry ' matched 213 probes ^C        11 svc.startd                                           2        13 svc.configd                                         2        42 netcfgd                                             2 (truncated output)      2610 gnome-terminal                                     1624      2549 java                                               2464      1221 Xorg                                              5246      2613 dtrace                                             5528      2054 htcacheclean                                       9503 To get the total number of read bytes per process, execute the following command: root@solaris11-1:~# dtrace -n 'sysinfo:::readch { @bytes[execname] = sum(arg0); }' dtrace: description 'sysinfo:::readch ' matched 4 probes ^C in.mpathd                                                     1 named                                                         56 sed                                                         100 wnck-applet                                                 157 (truncated output) VBoxService                                               20460 svc.startd                                                40320 Xorg                                                       65294 ps                                                       1096780 thunderbird-bin                                         3191863 To get the number of write bytes by process, run the following command: root@solaris11-1:~# dtrace -n 'sysinfo:::writech { @bytes[execname] = sum(arg0); }' dtrace: description 'sysinfo:::writech ' matched 4 probes ^C dtrace                                                        1 gnome-power-mana                                               8 xscreensaver                                                 36 gnome-session                                               367 clock-applet                                                404 named                                                       528 gvfsd                                                       748 (truncated output) metacity                                                   24616 ps                                                        59590 wnck-applet                                               65523 gconfd-2                                                   83234 Xorg                                                     184712 firefox                                                   403682 To know the number of pages paged-in by process, execute the following command: root@solaris11-1:~# dtrace -n 'vminfo:::pgpgin { @pg[execname] = sum(arg0); }' dtrace: description 'vminfo:::pgpgin ' matched 1 probe ^C (no output) To list the disk size by process, run the following command: root@solaris11-1:~# dtrace -n 'io:::start { printf("%d %s %d",pid,execname,args[0]->b_bcount); }' dtrace: description 'io:::start ' matched 3 probes CPU     ID                    FUNCTION:NAME 1   6962             bdev_strategy:start 5 zpool-rpool 4096 1   6962             bdev_strategy:start 5 zpool-rpool 4096 2   6962             bdev_strategy:start 5 zpool-rpool 4096 2   6962             bdev_strategy:start 2663 firefox 3584 2   6962             bdev_strategy:start 2663 firefox 3584 2   6962             bdev_strategy:start 2663 firefox 3072 2   6962             bdev_strategy:start 2663 firefox 4096 ^C (truncated output) From Brendan Gregg's website (http://www.brendangregg.com/dtrace.html), there are other good and excellent scripts. For example, prustat.d (which we can save in our home directory) is one of them and its output is self-explanatory; it can be obtained using the following commands: root@solaris11-1:~# chmod u+x prustat.d root@solaris11-1:~# ./prustat.d PID   %CPU   %Mem %Disk   %Net COMM 2537   0.91   2.38   0.00   0.00 java 1218   0.70   1.81   0.00   0.00 Xorg 2610   0.51   0.47   0.00   0.00 gnome-terminal 2522   0.00   0.96   0.00   0.00 nautilus 2523   0.01   0.78   0.00   0.00 updatemanagerno 2519   0.00   0.72   0.00   0.00 gnome-panel 1212   0.42   0.20   0.00   0.00 pkg.depotd 819   0.00   0.53   0.00   0.00 named 943   0.17   0.36   0.00  0.00 poold    13   0.01   0.47   0.00   0.00 svc.configd (truncated output) From the DTraceToolkit website (http://www.brendangregg.com/dtracetoolkit.html), we can download and save the topsysproc.d script in our home directory. Then, by executing it, we are able to find which processes execute more system calls, as shown in the following commands: root@solaris11-1:~/DTraceToolkit-0.99/Proc# ./topsysproc 10 2014 May 4 19:25:10, load average: 0.38, 0.30, 0.28   syscalls: 12648    PROCESS                        COUNT    isapython2.6                       20    sendmail                           20    dhcpd                               24    httpd.worker                       30    updatemanagernot                   40    nautilus                            42    xscreensaver                       50    tput                               59    gnome-settings-d                   62    metacity                           75    VBoxService                         81    ksh93                            118    clear                             163    poold                             201    pkg.depotd                         615    VBoxClient                         781    java                             1249    gnome-terminal                   2224    dtrace                           2712    Xorg                             3965 An overview of the recipe You learned how to monitor processes using several tools such as prstat, ps, and dtrace. Furthermore, you saw several commands that explain how to control and analyze a process. Managing processes' priority on Solaris 11 Oracle Solaris 11 allows us to change the priority of processes using the priocntl command either during the start of the process or after the process is run. Getting ready This recipe requires a virtual machine (VirtualBox or VMware) running Oracle Solaris 11 with a 2 GB RAM at least. It is recommended that the system have more than one processor or core. How to do it… In the Introduction section, we talked about scheduling classes and this time, we will see more information on this subject. To begin, list the existing and active classes by executing the following command: root@solaris11-1:~# priocntl -l CONFIGURED CLASSES ================== SYS (System Class) TS (Time Sharing) Configured TS User Priority Range: -60 through 60 SDC (System Duty-Cycle Class) FSS (Fair Share) Configured FSS User Priority Range: -60 through 60 FX (Fixed priority) Configured FX User Priority Range: 0 through 60 IA (Interactive) Configured IA User Priority Range: -60 through 60 RT (Real Time) Configured RT User Priority Range: 0 through 59 When handling priorities, which we learned in this article, only the positive part is important and we need to take care because the values shown in the previous output have their own class as the reference. Thus, they are not absolute values. To show a simple example, start a process with a determined class (FX) and priority (55) by executing the following commands: root@solaris11-1:~# priocntl -e -c FX -m 60 -p 55 gcalctool root@solaris11-1:~# ps -efcl | grep gcalctool 0 S     root 2660 2646   FX 55       ? 33241       ? 04:48:52 pts/1       0:01 gcalctool 0 S     root 2664 2661 FSS 22       ?   2149       ? 04:50:09 pts/2       0:00 grep gcalctool As can be seen previously, the process is using exactly the class and priority that we have chosen. Moreover, it is appropriate to explain some options such as -e (to execute a specified command), -c (to set the class), -p (the chosen priority inside the class), and -m (the maximum limit that the priority of a process can be raised to). The next exercise is to change the process priority after it starts. For example, by executing the following command, the top tool will be executed in the FX class with an assigned priority equal to 40, as shown in the following command: root@solaris11-1:~# priocntl -e -c FX -m 60 -p 40 top root@solaris11-1:~# ps -efcl | grep top 0 S     root 2662 2649   FX 40       ?   1112       ? 05:16:21 pts/3       0:00 top 0 S     root 2664 2606   IA 33       ?   2149       ? 05:16:28 pts/2       0:00 grep top Then, to change the priority that is running, execute the following command: root@solaris11-1:~# priocntl -s -p 50 2662 root@solaris11-1:~# ps -efcl | grep top 0 S     root 2662 2649   FX 50       ?   1112       ? 05:16:21 pts/3       0:00 top 0 S     root 2667 2606   IA 55       ?   2149       ? 05:17:00 pts/2       0:00 grep top This is perfect! The -s option is used to change the priorities' parameters, and the –p option assigns the new priority to the process. If we tried to use the TS class, the results would not have been the same because this test system does not have a serious load (it's almost idle) and in this case, the priority would be raised automatically to around 59. An overview of the recipe You learned how to configure a process class as well as change the process priority at the start and during its execution using the priocntl command. Configuring FSS and applying it to projects The FSS class is the best option to manage resource allocation (for example, CPU) on Oracle Solaris 11. In this section, we are going to learn how to use it. Getting ready This recipe requires a virtual machine (VirtualBox or VMware) running Oracle Solaris 11 with a 4 GB RAM at least. It is recommended that the system has only one processor or core. How to do it… In Oracle Solaris 11, the default scheduler class is TS, as shown by the following command: root@solaris11-1:~# dispadmin -d TS (Time Sharing) This default configuration comes from the /etc/dispadmin.conf file: root@solaris11-1:~# more /etc/dispadmin.conf # # /etc/dispadmin.conf # # Do NOT edit this file by hand -- use dispadmin(1m) instead. # DEFAULT_SCHEDULER=TS If we need to verify and change the default scheduler, we can accomplish this task by running the following commands: root@solaris11-1:~# dispadmin -d FSS root@solaris11-1:~# dispadmin -d FSS (Fair Share) root@solaris11-1:~# more /etc/dispadmin.conf # # /etc/dispadmin.conf # # Do NOT edit this file by hand -- use dispadmin(1m) instead. # DEFAULT_SCHEDULER=FSS Unfortunately, this new setting only takes effect for newly created processes that are run after the command, but current processes still are running using the previously configured classes (TS and IA), as shown in the following command: root@solaris11-1:~# ps -efcl -o s,uid,pid,zone,class,pri,comm | more S   UID   PID     ZONE CLS PRI COMMAND T     0     0   global SYS 96 sched S     0     5   global SDC 99 zpool-rpool S   0     6   global SDC 99 kmem_task S     0     1   global   TS 59 /usr/sbin/init S     0     2   global SYS 98 pageout S     0     3   global SYS 60 fsflush S     0     7   global SYS 60 intrd S     0     8   global SYS 60 vmtasks S 60002 1173  global   TS 59 /usr/lib/fm/notify/smtp-notify S     0   11   global   TS 59 /lib/svc/bin/svc.startd S     0   13   global   TS 59 /lib/svc/bin/svc.configd S   16   99   global   TS 59 /lib/inet/ipmgmtd S     0   108   global   TS 59 /lib/inet/in.mpathd S   17   40   global   TS 59 /lib/inet/netcfgd S     0   199   global   TS 59 /usr/sbin/vbiosd S     0   907   global   TS 59 /usr/lib/fm/fmd/fmd (truncated output) To change the settings from all current processes (the -i option) to using FSS (the -c option) without rebooting the system, execute the following command: root@solaris11-1:~# priocntl -s -c FSS -i all root@solaris11-1:~# ps -efcl -o s,uid,pid,zone,class,pri,comm | more S   UID   PID     ZONE CLS PRI COMMAND T     0     0   global SYS 96 sched S     0     5   global SDC 99 zpool-rpool S     0     6   global SDC 99 kmem_task S     0     1   global   TS 59 /usr/sbin/init S     0     2   global SYS 98 pageout S     0     3   global SYS 60 fsflush S     0     7   global SYS 60 intrd S     0     8   global SYS 60 vmtasks S 60002 1173   global FSS 29 /usr/lib/fm/notify/smtp-notify S     0   11   global FSS 29 /lib/svc/bin/svc.startd S     0   13   global FSS 29 /lib/svc/bin/svc.configd S   16   99   global FSS 29 /lib/inet/ipmgmtd S     0   108   global FSS 29 /lib/inet/in.mpathd S   17   40   global FSS 29 /lib/inet/netcfgd S     0   199   global FSS 29 /usr/sbin/vbiosd S     0   907   global FSS 29 /usr/lib/fm/fmd/fmd S     0 2459   global FSS 29 gnome-session S   15   66   global FSS 29 /usr/sbin/dlmgmtd S     1   88   global FSS 29 /lib/crypto/kcfd S     0   980   global FSS 29 /usr/lib/devchassis/devchassisd S     0   138   global FSS 29 /usr/lib/pfexecd S     0   277   global FSS 29 /usr/lib/zones/zonestatd O     0 2657   global FSS   1 more S   16   638   global FSS 29 /lib/inet/nwamd S   50 1963   global FSS 29 /usr/bin/dbus-launch S     0   291   global FSS 29 /usr/lib/dbus-daemon S     0   665   global FSS 29 /usr/lib/picl/picld (truncated output) It's almost done, but the init process (PID equal to 1) was not changed to the FSS class, unfortunately. This change operation is done manually, by executing the following commands: root@solaris11-1:~# priocntl -s -c FSS -i pid 1 root@solaris11-1:~# ps -efcl -o s,uid,pid,zone,class,pri,comm | more S   UID   PID     ZONE CLS PRI COMMAND T     0     0   global SYS 96 sched S     0     5   global SDC 99 zpool-rpool S     0     6   global SDC 99 kmem_task S     0     1   global FSS 29 /usr/sbin/init S     0     2   global SYS 98 pageout S     0     3   global SYS 60 fsflush S     0     7   global SYS 60 intrd S     0     8   global SYS 60 vmtasks S 60002 1173   global FSS 29 /usr/lib/fm/notify/smtp-notify S    0   11   global FSS 29 /lib/svc/bin/svc.startd S     0   13   global FSS 29 /lib/svc/bin/svc.configd S   16   99   global FSS 29 /lib/inet/ipmgmtd S     0   108   global FSS 29 /lib/inet/in.mpathd (truncated output) From here, it would be possible to use projects (a very nice concept from Oracle Solaris), tasks, and FSS to make an attractive example. It follows a quick demonstration. From an initial installation, Oracle Solaris 11 already has some default projects, as shown by the following commands: root@solaris11-1:~# projects user.root default root@solaris11-1:~# projects -l system projid : 0 comment: "" users : (none) groups : (none) attribs: user.root projid : 1 comment: "" users : (none) groups : (none) attribs: (truncated output) root@solaris11-1:~# more /etc/project system:0:::: user.root:1:::: noproject:2:::: default:3:::: group.staff:10:::: In this exercise, we are going to create four new projects: ace_proj_1, ace_proj_2, ace_proj_3, and ace_proj_4. For each project will be associated an amount of shares (40, 30, 20, and 10 respectively). Additionally, it will create some useless, but CPU-consuming tasks by starting a Firefox instance. Therefore, execute the following commands to perform the tasks: root@solaris11-1:~# projadd -U root -K "project.cpu-shares=(priv,40,none)" ace_proj_1 root@solaris11-1:~# projadd -U root -K "project.cpu-shares=(priv,30,none)" ace_proj_2 root@solaris11-1:~# projadd -U root -K "project.cpu-shares=(priv,20,none)" ace_proj_3 root@solaris11-1:~# projadd -U root -K "project.cpu-shares=(priv,10,none)" ace_proj_4 root@solaris11-1:~# projects user.root default ace_proj_1 ace_proj_2 ace_proj_3 ace_proj_4 Here is where the trick comes in. The FSS class only starts to act when: The total CPU consumption by all processes is over 100 percent The sum of processes from defined projects is over the current number of CPUs Thus, to be able to see the FSS effect, as explained previously, we have to repeat the next four commands several times (using the Bash history is suitable here), shown as follows: root@solaris11-1:~# newtask -p ace_proj_1 firefox & [1] 3016 root@solaris11-1:~# newtask -p ace_proj_2 firefox & [2] 3032 root@solaris11-1:~# newtask -p ace_proj_3 firefox & [3] 3037 root@solaris11-1:~# newtask -p ace_proj_4 firefox & [4] 3039 As time goes by and the number of tasks increase, each project will be approaching the FSS share limit (40 percent, 30 percent, 20 percent, and 10 percent of processor, respectively). We can follow this trend by executing the next command: root@solaris11-1:~# prstat -JR PID USERNAME SIZE   RSS STATE   PRI NICE     TIME CPU PROCESS/NLWP 3516 root     8552K 1064K cpu1     49   0   0:01:25 25% dd/1 3515 root     8552K 1064K run       1   0   0:01:29 7.8% dd/1 1215 root       89M   29M run     46   0   0:00:56 0.0% Xorg/3 2661 root       13M 292K sleep   59   0   0:00:28 0.0% VBoxClient/3    750 root       13M 2296K sleep   55   0   0:00:02 0.0% nscd/32 3518 root       11M 3636K cpu0     59   0 0:00:00 0.0% (truncated output) PROJID   NPROC SWAP   RSS MEMORY     TIME CPU PROJECT 100       4   33M 4212K   0.1%   0:01:49 35% ace_proj_1 101       4   33M 4392K   0.1%   0:01:14 28% ace_proj_2 102       4   33M 4204K   0.1%   0:00:53 20% ace_proj_3 103       4   33M 4396K   0.1%   0:00:30 11% ace_proj_4 3       2   10M 4608K   0.1%   0:00:06 0.8% default 1       41 2105M 489M   12%   0:00:09 0.7% user.root 0       78 780M 241M   5.8%   0:00:20 0.3% system The prstat command with the –J option shows a summary of the existing projects, and –R requires the kernel to execute the prstat command in the RT scheduling class. If the reader faces some problem getting the expected results, it is possible to swap the firefox command with the dd if=/dev/zero of=/dev/null & command to get the same results. It is important to highlight that while not all projects take their full share of the CPU, other projects can borrow some shares (percentages). This is the reason why ace_proj_4 has 11 percent, because ace_proj_1 has taken only 35 percent ( the maximum is 40 percent). An overview of the recipe In this section, you learned how to change the default scheduler from TS to FSS in a temporary and persistent way. Finally, you saw a complete example using projects, tasks, and FSS. References Solaris Performance and Tools: DTrace and MDB Techniques for Solaris 10 and OpenSolaris; Brendan Gregg, Jim Mauro, Richard McDougall; Prentice Hall; ISBN-13: 978-0131568198 DTraceToolkit website at http://www.brendangregg.com/dtracetoolkit.html Dtrace.org website at http://dtrace.org/blogs/ Summary In this article we learned to monitor and handle process execution, to manage process priority on Solaris 11, and configure FSS and apply it to projects. Resources for Article: Further resources on this subject: Securing Data at Rest in Oracle 11g [article] Getting Started with Oracle GoldenGate [article] Remote Job Agent in Oracle 11g Database with Oracle Scheduler [article]
Read more
  • 0
  • 0
  • 5300
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
article-image-creating-routers
Packt
08 Oct 2014
11 min read
Save for later

Creating Routers

Packt
08 Oct 2014
11 min read
In this article by James Denton, author of the book, Learning OpenStack, Networking (Neutron), we will create Neutron routers and attach them to networks. The Neutron L3 agent enables IP routing and NAT support for instances within the cloud by utilizing network namespaces to provide isolated routing instances. By creating networks and attaching them to routers, tenants can expose connected instances and their applications to the Internet. The neutron-l3-agent service was installed on the controller node as part of the overall Neutron installation process. (For more resources related to this topic, see here.) Configuring the Neutron L3 agent Before the neutron-l3-agent service can be started, it must be configured. Neutron stores the L3 agent configuration in the /etc/neutron/l3_agent.ini file. The most common configuration options will be covered here. Defining an interface driver Like previously installed agents, the Neutron L3 agent must be configured to use an interface driver that corresponds to the chosen networking plugin. Using crudini, configure the Neutron L3 agent to use one of the following drivers: For LinuxBridge: # crudini --set /etc/neutron/l3_agent.ini DEFAULT interface_driver neutron.agent.linux.interface.BridgeInterfaceDriver For Open vSwitch: # crudini --set /etc/neutron/l3_agent.ini DEFAULT interface_driver neutron.agent.linux.interface.OVSInterfaceDriver Setting the external network The external network connected to a router is one that not only provides external connectivity to the router and the instances behind it, but also serves as the network from which floating IPs are derived. In Havana, each L3 agent in the cloud can be associated with only one external network. In Icehouse, L3 agents are capable of supporting multiple external networks. To be eligible to serve as an external network, a provider network must have been configured with its router:external attribute set to true. In Havana, if more than one provider network has the attribute set to true, then the gateway_external_network_id configuration option must be used to associate an external network to the agent. To define a specific external network, configure the gateway_external_network_id option as follows: gateway_external_network_id = <UUID of eligible provider network> In Havana, if this option is left empty, the agent will enforce that only a single external networks exists. The agent will automatically use the network for which the router:external attribute is set to true. The default configuration contains an empty or unset value and is sufficient for now. Setting the external bridge The L3 agent must be aware of how to connect the external interface of a router to the network. The external_network_bridge configuration option defines a bridge on the host in which the external interface will be connected. In earlier releases of Havana, the default value of external_network_bridge was br-ex, a bridge expected to be configured manually outside of OpenStack and intended to be dedicated to the external network. As a result of the bridge not being fully managed by OpenStack, provider attributes of the network created within Neutron, including the segmentation ID, network type, and the provider bridge itself, are ignored. To fully utilize a provider network and its attributes, the external_network_bridge configuration option should be set to an empty, or blank, value. By doing so, Neutron will adhere to the attributes of the network and place the external interface of routers into a bridge that it creates, along with a physical or virtual VLAN interface used to provide external connectivity. When using Open vSwitch, the external interface of the router is placed in the integration bridge and assigned to the appropriate local VLAN. With the LinuxBridge plugin, the external interface of routers is placed into a Linux bridge that corresponds to the external network. Using crudini, set the external_network_bridge configuration option to an empty value as follows: # crudini --set /etc/neutron/l3_agent.ini DEFAULT external_network_bridge Enabling the metadata proxy When Neutron routers are used as the gateway for instances, requests for metadata are proxied by the router and forwarded to the Nova metadata service. This feature is enabled by default and can be disabled by setting the enable_metadata_proxy value to false in the l3_agent.ini configuration file. Starting the Neutron L3 agent To start the neutron-l3-agent service and configure it to start at boot, issue the following commands on the controller node: # service neutron-l3-agent start # chkconfig neutron-l3-agent on Verify the agent is running: # service neutron-l3-agent status The service should return an output similar to the following: [root@controller neutron]# service neutron-l3-agent status neutron-l3-agent (pid 13501) is running... If the service remains stopped, troubleshoot any issues that may be found in the /var/log/neutron/l3-agent.log log file. Router management in the CLI Neutron offers a number of commands that can be used to create and manage routers. The primary commands associated with router management include: router-create router-delete router-gateway-clear router-gateway-set router-interface-add router-interface-delete router-list router-list-on-l3-agent router-port-list router-show router-update Creating routers in the CLI Routers in Neutron are associated with tenants and are available for use only by users within the tenant that created them. As an administrator, you can create routers on behalf of tenants during the creation process. To create a router, use the router-create command as follows: Syntax: router-create [--tenant-id TENANT_ID] [--admin-state-down] NAME Working with router interfaces in the CLI Neutron routers have two types of interfaces: gateway and internal. The gateway interface of a Neutron router is analogous to the WAN interface of a hardware router. It is the interface connected to an upstream device that provides connectivity to external resources. The internal interfaces of Neutron routers are analogous to the LAN interfaces of hardware routers. Internal interfaces are connected to tenant networks and often serve as the gateway for connected instances. Attaching internal interfaces to routers To create an interface in the router and attach it to a subnet, use the router-interface-add command as follows: Syntax: router-interface-add <router-id> <INTERFACE> In this case, INTERFACE is the ID of the subnet to be attached to the router. In Neutron, a network may contain multiple subnets. It is important to attach the router to each subnet so that it properly serves as the gateway for those subnets. Once the command is executed, Neutron creates a port in the database that is associated with the router interface. The L3 agent is responsible for connecting interfaces within the router namespace to the proper bridge. Attaching a gateway interface to a router The external interface of a Neutron router is referred to as the gateway interface. A router is limited to a single gateway interface. To be eligible for use as an external network that can be used for gateway interfaces, a provider network must have its router:external attribute set to true. To attach a gateway interface to a router, use the router-gateway-set command as follows: Syntax: router-gateway-set <router-id> <external-network-id> [--disable-snat] The default behavior of a Neutron router is to source NAT all outbound traffic from instances that do not have a corresponding floating IP. To disable this functionality, append --disable-snat to the router-gateway-set command. Listing interfaces attached to routers To list the interfaces attached to routers, use the router-port-list command as follows: Syntax: router-port-list <router-id> The returned output includes the Neutron port ID, MAC address, IP address, and associated subnet of attached interfaces. Deleting internal interfaces To delete an internal interface from a router, use the router-interface-delete command as follows: Syntax: router-interface-delete <router-id> <INTERFACE> Here, INTERFACE is the ID of the subnet to be removed from the router. Deleting an interface from a router results in the associated Neutron port being removed from the database. Clearing the gateway interface Gateway interfaces cannot be removed from a router using the router-interface-delete command. Instead, the router-gateway-clear command must be used. To clear the gateway of a router, use the router-gateway-clear command as follows: Syntax: router-gateway-clear <router-id> Neutron includes checks that will prohibit the clearing of a gateway interface in the event that floating IPs or other resources from the network are associated with the router. Listing routers in the CLI To display a list of existing routers, use the Neutron router-list command as follows: Syntax: router-list [--tenant-id TENANT_ID] The returned output includes the router ID, name, external gateway network, and the SNAT state. Users will only see routers that exist in their tenant or project. When executed by an administrator, Neutron will return a listing of all routers across all tenants unless, the tenant ID is specified. Displaying router attributes in the CLI To display the attributes of a router, use the Neutron router-show command as follows: Syntax: router-show <router id> Among the output returned is the admin state, the external network, the SNAT state, and the tenant ID associated with the router. Updating router attributes in the CLI To update the attributes of a router, use the Neutron router-update command as follows: Syntax: router-update <router id> [--admin-state-up] [--routes destination=<network/cidr>,nexthop=<gateway_ip>] The admin-state-up attribute is a Boolean, which when set to false, does not allow Neutron to update interfaces within the router. This includes not adding floating IPs or additional internal interfaces to the router. Setting the value to true will allow queued changes to be applied. The routes option allows you to add static routes to the routing table of a Neutron router. To add static routes, use the following syntax: Syntax: neutron router-update <router id> --routes type=dict list=true destination=<network/cidr>,nexthop=<gateway_ip> Adding static routes to a router is an undocumented and broken feature in Havana. In Havana, the command results in the route being added to the database and router show output, while not being added to the routing table. To resolve this, add the following line to the [DEFAULT] block of the /etc/neutron/l3_agent.ini configuration file: root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf Restart the neutron-l3-agent service for changes to take effect. Deleting routers in the CLI To delete a router, use the Neutron router-delete command as follows: Syntax: router-delete <router id> Before a router can be deleted, all floating IPs and internal interfaces associated with the router must be unassociated or deleted. This may require deleting instances or detaching connected interfaces from instances. Network Address Translation Network Address Translation (NAT) is a networking concept that was developed in the early 1990s in response to the rapid depletion of IP addresses throughout the world. Prior to NAT, every host connected to the Internet had a unique IP address. OpenStack routers support two types of NAT: one-to-one many-to-one A one-to-one NAT is a method in which one IP address is directly mapped to another. Commonly referred to as a static NAT, a one-to-one NAT is often used to map a unique public address to a privately addressed host. Floating IPs utilize one-to-one NAT concepts. A many-to-one NAT is a method in which multiple addresses are mapped to a single address. A many-to-one NAT employs the use of port address translation (PAT). Neutron uses PAT to provide external access to instances behind the router when floating IPs are not assigned. For more information on network address translation, please visit Wikipedia at http://en.wikipedia.org/wiki/Network_address_translation. Floating IP addresses Tenant networks, when attached to a Neutron router, are meant to utilize the router as their default gateway. By default, when a router receives traffic from an instance and routes it upstream, the router performs a port address translation and modifies the source address of the packet to appear as its own external interface address. This ensures that the packet can be routed upstream and returned to the router, where it will modify the destination address to be that of the instance that initiated the connection. Neutron refers to this type of behavior as Source NAT. When users require direct inbound access to instances, a floating IP address can be utilized. A floating IP address in OpenStack is a static NAT that maps an external address to an internal address. This method of NAT allows instances to be reachable from external networks, such as the Internet. Floating IP addresses are configured on the external interface of the router that serves as the gateway for the instance, which is then responsible for modifying the source and/or destination address of packets depending on their direction. In this article, we learned that Neutron routers being a core component of networking in OpenStack, provides tenants the flexibility to design the network to best suit their application. Resources for Article: Further resources on this subject: Using OpenStack Swift [article] The OpenFlow Controllers [article] Troubleshooting [article]
Read more
  • 0
  • 0
  • 11682

article-image-typical-sales-cycle-and-territory-management
Packt
08 Oct 2014
6 min read
Save for later

A typical sales cycle and territory management

Packt
08 Oct 2014
6 min read
In this article by Mohith Shrivastava, the author of Salesforce Essentials for Administrators, we will look into the typical sales cycle and the territory management feature of Salesforce. (For more resources related to this topic, see here.) A typical sales cycle starts from a campaign. An example of a campaign can be a conference or a seminar where marketing individuals explain the product offering of the company to their prospects. Salesforce provides a campaign object to store this data. A campaign may involve different processes, and the campaign management module of Salesforce is simple. A matured campaign management system will have features such as sending e-mails to campaign members in bulk, and tracking how many people really opened and viewed the e-mails, and how many of them responded to the e-mails. Some of these processes can be custom built in Salesforce, but out of the box, Salesforce has a campaign member object apart from the campaign where members are selected by marketing reps. Members can be leads or contacts of Salesforce. A campaign generates leads. Leads are the prospects that have shown interest in the products and offerings of the company. The lead management module provides a lead object to store all the leads in the system. These prospects are converted into accounts, contacts, and opportunities when the prospect qualifies as an account. Salesforce provides a Lead Convert button to convert these leads into accounts, contacts, and opportunities. Features such as Web-to-Lead provided by the platform are ideal for capturing leads in Salesforce. Accounts can be B2B (business to business) or B2C (business to consumer). B2C in Salesforce is represented as person accounts. This is a special feature that needs to be enabled by a request from Salesforce. It's a record type where person accounts fields are from contacts. Contacts are people, and they are stored in objects in the contact object. They have a relationship with accounts (a relationship can be both master-detail as well as lookup.) An opportunity generates revenue if its status is closed won. Salesforce provides an object known as opportunities to store a business opportunity. The sales reps typically work on these opportunities, and their job is to close these deals and generate revenue. Opportunities have a stage field and stages start from prospecting to closed won or closed lost. Opportunity management provided by Salesforce consists of objects such as opportunity line items, products, price books, and price book entries. Products in Salesforce are the objects that are used as a lookup to junction objects such as an opportunity line item. An opportunity line item is a junction between an opportunity and a line item. Price books are price listings for products in Salesforce. A product can have a standard or custom price book. Custom price books are helpful when your company is offering products at discounts or varied prices for different customers based on market segmentation. Salesforce also provides a quote management module that consists of a quote object and quote line items that sales reps can use to send quotes to customers. The Order management module is new to the Salesforce CRM, and Salesforce provides an object known as orders that can generate an order from the draft state to the active state on accounts and contracts. Most companies use an ERP such as a SAP system to do order management. However, now, Salesforce has introduced this new feature, so on closed opportunities from accounts, you can create orders. The following screenshot explains the sales process and the sales life cycle from campaign to opportunity management:   To read more, I would recommend that you go through the Salesforce documentation available at http://www.salesforce.com/ap/assets/pdf/cloudforce/SalesCloud-TheSalesCloud.pdf. Territory management This feature is very helpful for organizations that run sales processes by sales territories. Let's say you have an account and your organization has a private sharing model. The account has to be worked on by sales representatives of the eastern as well as western regions. Presently, the owner is the sales rep of the eastern region, and because of the private sharing model, the sales rep of the western region will not have access. We could have used sharing rules to provide access, but the challenge is also to do a forecasting of the revenue generated from opportunities for both reps, and this is where writing sharing rules simply won't help us. We need the territory management feature of Salesforce for this, where you can retain opportunities and transfer representatives across territories, draw reports based on territories, and share accounts across territories extending the private sharing model. The key feature of this module is that it works with customizable forecasting only. Basic configurations We will explore the basic configuration needed to set up territory management. This feature is not enabled in your instance by default. To enable it, you have to log a case with Salesforce and explain its need. The basic navigation path for the territories feature is Setup | Manage Users | Manage Territories. Under Manage Territories, we have the settings to set the default access level for accounts, contacts, opportunities, and cases. This implies that when a new territory is created, the access level will be based on the default settings configured. There is a checkbox named Forecast managers can manage territories. Once checked, forecast managers can add accounts to territories, manage account assignment rules, and manage users. Under Manage Territories | Settings, you can see two different buttons, which are as follows: Enable Territory Management: This button forecasts hierarchy, and data is copied to the territory hierarchy. Each forecast hierarchy role will have a territory automatically created. Enable Territory Management from Scratch: This is for new organizations. On clicking this button, the forecast data is wiped, and please note that this is irreversible. Based on the role of the user, a territory is automatically assigned to the user. On the Territory Details page, one can use Add Users to assign users to territories. Account assignment rules To write account assignment rules, navigate to Manage Territories | Hierarchy. Select a territory and click on Manage Rules in the list related to the account assignment rules. Enter the rule name and define the filter criteria based on the account field. You can apply these rules to child territories if you check the Apply to Child Territories checkbox. There is a lot more to explore on this topic, but that's beyond the scope of this book. To explore more, I would recommend that you read the documentation from Salesforce available at https://na9.salesforce.com/help/pdfs/en/salesforce_territories_implementation_guide.pdf. Summary In this article, we have looked at how we can use the territory management feature of Salesforce. We have also described a typical sales cycle. Resources for Article: Further resources on this subject: Introducing Salesforce Chatter [article] Salesforce CRM Functions [article] Configuration in Salesforce CRM [article]
Read more
  • 0
  • 0
  • 2429

article-image-how-to-create-flappy-bird-clone-with-melonjs
Ellison Leao
26 Sep 2014
18 min read
Save for later

How to Create a Flappy Bird Clone with MelonJS

Ellison Leao
26 Sep 2014
18 min read
How to create a Flappy Bird clone using MelonJS Web game frameworks such as MelonJS are becoming more popular every day. In this post I will show you how easy it is to create a Flappy Bird clone game using the MelonJS bag of tricks. I will assume that you have some experience with JavaScript and that you have visited the melonJS official page. All of the code shown in this post is available on this GitHub repository. Step 1 - Organization A MelonJS game can be divided into three basic objects: Scene objects: Define all of the game scenes (Play, Menus, Game Over, High Score, and so on) Game entities: Add all of the stuff that interacts on the game (Players, enemies, collectables, and so on) Hud entities: All of the HUD objects to be inserted on the scenes (Life, Score, Pause buttons, and so on) For our Flappy Bird game, first create a directory, flappybirdgame, on your machine. Then create the following structure: flabbybirdgame | |--js |--|--entities |--|--screens |--|--game.js |--|--resources.js |--data |--|--img |--|--bgm |--|--sfx |--lib |--index.html Just a quick explanation about the folders: The js contains all of the game source. The entities folder will handle the HUD and the Game entities. In the screen folder, we will create all of the scene files. The game.js is the main game file. It will initialize all of the game resources, which is created in the resources.js file, the input, and the loading of the first scene. The data folder is where all of the assets, sounds, and game themes are inserted. I divided the folders into img for images (backgrounds, player atlas, menus, and so on), bgm for background music files (we need to provide a .ogg and .mp3 file for each sound if we want full compatibility with all browsers) and sfx for sound effects. In the lib folder we will add the current 1.0.2 version of MelonJS. Lastly, an index.html file is used to build the canvas. Step 2 - Implementation First we will build the game.js file: var game = { data: { score : 0, steps: 0, start: false, newHiScore: false, muted: false }, "onload": function() { if (!me.video.init("screen", 900, 600, true, 'auto')) { alert("Your browser does not support HTML5 canvas."); return; } me.audio.init("mp3,ogg"); me.loader.onload = this.loaded.bind(this); me.loader.preload(game.resources); me.state.change(me.state.LOADING); }, "loaded": function() { me.state.set(me.state.MENU, new game.TitleScreen()); me.state.set(me.state.PLAY, new game.PlayScreen()); me.state.set(me.state.GAME_OVER, new game.GameOverScreen()); me.input.bindKey(me.input.KEY.SPACE, "fly", true); me.input.bindKey(me.input.KEY.M, "mute", true); me.input.bindPointer(me.input.KEY.SPACE); me.pool.register("clumsy", BirdEntity); me.pool.register("pipe", PipeEntity, true); me.pool.register("hit", HitEntity, true); // in melonJS 1.0.0, viewport size is set to Infinity by default me.game.viewport.setBounds(0, 0, 900, 600); me.state.change(me.state.MENU); } }; The game.js is divided into: data object: This global object will handle all of the global variables that will be used on the game. For our game we will use score to record the player score, and steps to record how far the bird goes. The other variables are flags that we are using to control some game states. onload method: This method preloads the resources and initializes the canvas screen and then calls the loaded method when it's done. loaded method: This method first creates and puts into the state stack the screens that we will use on the game. We will use the implementation for these screens later on. It enables all of the input keys to handle the game. For our game we will be using the space and left mouse keys to control the bird and the M key to mute sound. It also adds the game entities BirdEntity, PipeEntity and the HitEntity in the game poll. I will explain the entities later. Then you need to create the resource.js file: game.resources = [ {name: "bg", type:"image", src: "data/img/bg.png"}, {name: "clumsy", type:"image", src: "data/img/clumsy.png"}, {name: "pipe", type:"image", src: "data/img/pipe.png"}, {name: "logo", type:"image", src: "data/img/logo.png"}, {name: "ground", type:"image", src: "data/img/ground.png"}, {name: "gameover", type:"image", src: "data/img/gameover.png"}, {name: "gameoverbg", type:"image", src: "data/img/gameoverbg.png"}, {name: "hit", type:"image", src: "data/img/hit.png"}, {name: "getready", type:"image", src: "data/img/getready.png"}, {name: "new", type:"image", src: "data/img/new.png"}, {name: "share", type:"image", src: "data/img/share.png"}, {name: "tweet", type:"image", src: "data/img/tweet.png"}, {name: "leader", type:"image", src: "data/img/leader.png"}, {name: "theme", type: "audio", src: "data/bgm/"}, {name: "hit", type: "audio", src: "data/sfx/"}, {name: "lose", type: "audio", src: "data/sfx/"}, {name: "wing", type: "audio", src: "data/sfx/"}, ]; Now let's create the game entities. First the HUD elements: create a HUD.js file in the entities folder. In this file you will create: A score entity A background layer entity The share buttons entities (Facebook, Twitter, and so on) game.HUD = game.HUD || {}; game.HUD.Container = me.ObjectContainer.extend({ init: function() { // call the constructor this.parent(); // persistent across level change this.isPersistent = true; // non collidable this.collidable = false; // make sure our object is always draw first this.z = Infinity; // give a name this.name = "HUD"; // add our child score object at the top left corner this.addChild(new game.HUD.ScoreItem(5, 5)); } }); game.HUD.ScoreItem = me.Renderable.extend({ init: function(x, y) { // call the parent constructor // (size does not matter here) this.parent(new me.Vector2d(x, y), 10, 10); // local copy of the global score this.stepsFont = new me.Font('gamefont', 80, '#000', 'center'); // make sure we use screen coordinates this.floating = true; }, update: function() { return true; }, draw: function (context) { if (game.data.start && me.state.isCurrent(me.state.PLAY)) this.stepsFont.draw(context, game.data.steps, me.video.getWidth()/2, 10); } }); var BackgroundLayer = me.ImageLayer.extend({ init: function(image, z, speed) { name = image; width = 900; height = 600; ratio = 1; // call parent constructor this.parent(name, width, height, image, z, ratio); }, update: function() { if (me.input.isKeyPressed('mute')) { game.data.muted = !game.data.muted; if (game.data.muted){ me.audio.disable(); }else{ me.audio.enable(); } } return true; } }); var Share = me.GUI_Object.extend({ init: function(x, y) { var settings = {}; settings.image = "share"; settings.spritewidth = 150; settings.spriteheight = 75; this.parent(x, y, settings); }, onClick: function(event) { var shareText = 'Just made ' + game.data.steps + ' steps on Clumsy Bird! Can you beat me? Try online here!'; var url = 'http://ellisonleao.github.io/clumsy-bird/'; FB.ui( { method: 'feed', name: 'My Clumsy Bird Score!', caption: "Share to your friends", description: ( shareText ), link: url, picture: 'http://ellisonleao.github.io/clumsy-bird/data/img/clumsy.png' } ); return false; } }); var Tweet = me.GUI_Object.extend({ init: function(x, y) { var settings = {}; settings.image = "tweet"; settings.spritewidth = 152; settings.spriteheight = 75; this.parent(x, y, settings); }, onClick: function(event) { var shareText = 'Just made ' + game.data.steps + ' steps on Clumsy Bird! Can you beat me? Try online here!'; var url = 'http://ellisonleao.github.io/clumsy-bird/'; var hashtags = 'clumsybird,melonjs' window.open('https://twitter.com/intent/tweet?text=' + shareText + '&hashtags=' + hashtags + '&count=' + url + '&url=' + url, 'Tweet!', 'height=300,width=400') return false; } }); You should notice that there are different me classes for different types of entities. The ScoreItem is a Renderable object that is created under an ObjectContainer and it will render the game steps on the play screen that we will create later. The share and Tweet buttons are created with the GUI_Object class. This class implements the onClick event that handles click events used to create the share events. The BackgroundLayer is a particular object created using the ImageLayer class. This class controls some generic image layers that can be used in the game. In our particular case we are just using a single fixed image, with fixed ratio and no scrolling. Now to the game entities. For this game we will need: BirdEntity: The bird and its behavior PipeEntity: The pipe object HitEntity: A invisible entity just to get the steps counting PipeGenerator: Will handle the PipeEntity creation Ground: A entity for the ground TheGround: The animated ground Container Add an entities.js file into the entities folder: var BirdEntity = me.ObjectEntity.extend({ init: function(x, y) { var settings = {}; settings.image = me.loader.getImage('clumsy'); settings.width = 85; settings.height = 60; settings.spritewidth = 85; settings.spriteheight= 60; this.parent(x, y, settings); this.alwaysUpdate = true; this.gravity = 0.2; this.gravityForce = 0.01; this.maxAngleRotation = Number.prototype.degToRad(30); this.maxAngleRotationDown = Number.prototype.degToRad(90); this.renderable.addAnimation("flying", [0, 1, 2]); this.renderable.addAnimation("idle", [0]); this.renderable.setCurrentAnimation("flying"); this.animationController = 0; // manually add a rectangular collision shape this.addShape(new me.Rect(new me.Vector2d(5, 5), 70, 50)); // a tween object for the flying physic effect this.flyTween = new me.Tween(this.pos); this.flyTween.easing(me.Tween.Easing.Exponential.InOut); }, update: function(dt) { // mechanics if (game.data.start) { if (me.input.isKeyPressed('fly')) { me.audio.play('wing'); this.gravityForce = 0.01; var currentPos = this.pos.y; // stop the previous one this.flyTween.stop() this.flyTween.to({y: currentPos - 72}, 100); this.flyTween.start(); this.renderable.angle = -this.maxAngleRotation; } else { this.gravityForce += 0.2; this.pos.y += me.timer.tick * this.gravityForce; this.renderable.angle += Number.prototype.degToRad(3) * me.timer.tick; if (this.renderable.angle > this.maxAngleRotationDown) this.renderable.angle = this.maxAngleRotationDown; } } var res = me.game.world.collide(this); if (res) { if (res.obj.type != 'hit') { me.device.vibrate(500); me.state.change(me.state.GAME_OVER); return false; } // remove the hit box me.game.world.removeChildNow(res.obj); // the give dt parameter to the update function // give the time in ms since last frame // use it instead ? game.data.steps++; me.audio.play('hit'); } else { var hitGround = me.game.viewport.height - (96 + 60); var hitSky = -80; // bird height + 20px if (this.pos.y >= hitGround || this.pos.y <= hitSky) { me.state.change(me.state.GAME_OVER); return false; } } return this.parent(dt); }, }); var PipeEntity = me.ObjectEntity.extend({ init: function(x, y) { var settings = {}; settings.image = me.loader.getImage('pipe'); settings.width = 148; settings.height= 1664; settings.spritewidth = 148; settings.spriteheight= 1664; this.parent(x, y, settings); this.alwaysUpdate = true; this.gravity = 5; this.updateTime = false; }, update: function(dt) { // mechanics this.pos.add(new me.Vector2d(-this.gravity * me.timer.tick, 0)); if (this.pos.x < -148) { me.game.world.removeChild(this); } return true; }, }); var PipeGenerator = me.Renderable.extend({ init: function() { this.parent(new me.Vector2d(), me.game.viewport.width, me.game.viewport.height); this.alwaysUpdate = true; this.generate = 0; this.pipeFrequency = 92; this.pipeHoleSize = 1240; this.posX = me.game.viewport.width; }, update: function(dt) { if (this.generate++ % this.pipeFrequency == 0) { var posY = Number.prototype.random( me.video.getHeight() - 100, 200 ); var posY2 = posY - me.video.getHeight() - this.pipeHoleSize; var pipe1 = new me.pool.pull("pipe", this.posX, posY); var pipe2 = new me.pool.pull("pipe", this.posX, posY2); var hitPos = posY - 100; var hit = new me.pool.pull("hit", this.posX, hitPos); pipe1.renderable.flipY(); me.game.world.addChild(pipe1, 10); me.game.world.addChild(pipe2, 10); me.game.world.addChild(hit, 11); } return true; }, }); var HitEntity = me.ObjectEntity.extend({ init: function(x, y) { var settings = {}; settings.image = me.loader.getImage('hit'); settings.width = 148; settings.height= 60; settings.spritewidth = 148; settings.spriteheight= 60; this.parent(x, y, settings); this.alwaysUpdate = true; this.gravity = 5; this.updateTime = false; this.type = 'hit'; this.renderable.alpha = 0; this.ac = new me.Vector2d(-this.gravity, 0); }, update: function() { // mechanics this.pos.add(this.ac); if (this.pos.x < -148) { me.game.world.removeChild(this); } return true; }, }); var Ground = me.ObjectEntity.extend({ init: function(x, y) { var settings = {}; settings.image = me.loader.getImage('ground'); settings.width = 900; settings.height= 96; this.parent(x, y, settings); this.alwaysUpdate = true; this.gravity = 0; this.updateTime = false; this.accel = new me.Vector2d(-4, 0); }, update: function() { // mechanics this.pos.add(this.accel); if (this.pos.x < -this.renderable.width) { this.pos.x = me.video.getWidth() - 10; } return true; }, }); var TheGround = Object.extend({ init: function() { this.ground1 = new Ground(0, me.video.getHeight() - 96); this.ground2 = new Ground(me.video.getWidth(), me.video.getHeight() - 96); me.game.world.addChild(this.ground1, 11); me.game.world.addChild(this.ground2, 11); }, update: function () { return true; } }) Note that every game entity inherits from the me.ObjectEntity class. We need to pass the settings of the entity on the init method, telling it which image we will use from the resources along with the image measure. We also implement the update method for each Entity, telling it how it will behave during game time. Now we need to create our scenes. The game is divided into: TitleScreen PlayScreen GameOverScreen We will separate the scenes into js files. First create a title.js file in the screens folder: game.TitleScreen = me.ScreenObject.extend({ init: function(){ this.font = null; }, onResetEvent: function() { me.audio.stop("theme"); game.data.newHiScore = false; me.game.world.addChild(new BackgroundLayer('bg', 1)); me.input.bindKey(me.input.KEY.ENTER, "enter", true); me.input.bindKey(me.input.KEY.SPACE, "enter", true); me.input.bindPointer(me.input.mouse.LEFT, me.input.KEY.ENTER); this.handler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) { if (action === "enter") { me.state.change(me.state.PLAY); } }); //logo var logoImg = me.loader.getImage('logo'); var logo = new me.SpriteObject ( me.game.viewport.width/2 - 170, -logoImg, logoImg ); me.game.world.addChild(logo, 10); var logoTween = new me.Tween(logo.pos).to({y: me.game.viewport.height/2 - 100}, 1000).easing(me.Tween.Easing.Exponential.InOut).start(); this.ground = new TheGround(); me.game.world.addChild(this.ground, 11); me.game.world.addChild(new (me.Renderable.extend ({ // constructor init: function() { // size does not matter, it's just to avoid having a zero size // renderable this.parent(new me.Vector2d(), 100, 100); //this.font = new me.Font('Arial Black', 20, 'black', 'left'); this.text = me.device.touch ? 'Tap to start' : 'PRESS SPACE OR CLICK LEFT MOUSE BUTTON TO START ntttttttttttPRESS "M" TO MUTE SOUND'; this.font = new me.Font('gamefont', 20, '#000'); }, update: function () { return true; }, draw: function (context) { var measure = this.font.measureText(context, this.text); this.font.draw(context, this.text, me.game.viewport.width/2 - measure.width/2, me.game.viewport.height/2 + 50); } })), 12); }, onDestroyEvent: function() { // unregister the event me.event.unsubscribe(this.handler); me.input.unbindKey(me.input.KEY.ENTER); me.input.unbindKey(me.input.KEY.SPACE); me.input.unbindPointer(me.input.mouse.LEFT); me.game.world.removeChild(this.ground); } }); Then, create a play.js file on the same folder: game.PlayScreen = me.ScreenObject.extend({ init: function() { me.audio.play("theme", true); // lower audio volume on firefox browser var vol = me.device.ua.contains("Firefox") ? 0.3 : 0.5; me.audio.setVolume(vol); this.parent(this); }, onResetEvent: function() { me.audio.stop("theme"); if (!game.data.muted){ me.audio.play("theme", true); } me.input.bindKey(me.input.KEY.SPACE, "fly", true); game.data.score = 0; game.data.steps = 0; game.data.start = false; game.data.newHiscore = false; me.game.world.addChild(new BackgroundLayer('bg', 1)); this.ground = new TheGround(); me.game.world.addChild(this.ground, 11); this.HUD = new game.HUD.Container(); me.game.world.addChild(this.HUD); this.bird = me.pool.pull("clumsy", 60, me.game.viewport.height/2 - 100); me.game.world.addChild(this.bird, 10); //inputs me.input.bindPointer(me.input.mouse.LEFT, me.input.KEY.SPACE); this.getReady = new me.SpriteObject( me.video.getWidth()/2 - 200, me.video.getHeight()/2 - 100, me.loader.getImage('getready') ); me.game.world.addChild(this.getReady, 11); var fadeOut = new me.Tween(this.getReady).to({alpha: 0}, 2000) .easing(me.Tween.Easing.Linear.None) .onComplete(function() { game.data.start = true; me.game.world.addChild(new PipeGenerator(), 0); }).start(); }, onDestroyEvent: function() { me.audio.stopTrack('theme'); // free the stored instance this.HUD = null; this.bird = null; me.input.unbindKey(me.input.KEY.SPACE); me.input.unbindPointer(me.input.mouse.LEFT); } }); Finally, the gameover.js screen: game.GameOverScreen = me.ScreenObject.extend({ init: function() { this.savedData = null; this.handler = null; }, onResetEvent: function() { me.audio.play("lose"); //save section this.savedData = { score: game.data.score, steps: game.data.steps }; me.save.add(this.savedData); // clay.io if (game.data.score > 0) { me.plugin.clay.leaderboard('clumsy'); } if (!me.save.topSteps) me.save.add({topSteps: game.data.steps}); if (game.data.steps > me.save.topSteps) { me.save.topSteps = game.data.steps; game.data.newHiScore = true; } me.input.bindKey(me.input.KEY.ENTER, "enter", true); me.input.bindKey(me.input.KEY.SPACE, "enter", false) me.input.bindPointer(me.input.mouse.LEFT, me.input.KEY.ENTER); this.handler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) { if (action === "enter") { me.state.change(me.state.MENU); } }); var gImage = me.loader.getImage('gameover'); me.game.world.addChild(new me.SpriteObject( me.video.getWidth()/2 - gImage.width/2, me.video.getHeight()/2 - gImage.height/2 - 100, gImage ), 12); var gImageBoard = me.loader.getImage('gameoverbg'); me.game.world.addChild(new me.SpriteObject( me.video.getWidth()/2 - gImageBoard.width/2, me.video.getHeight()/2 - gImageBoard.height/2, gImageBoard ), 10); me.game.world.addChild(new BackgroundLayer('bg', 1)); this.ground = new TheGround(); me.game.world.addChild(this.ground, 11); // share button var buttonsHeight = me.video.getHeight() / 2 + 200; this.share = new Share(me.video.getWidth()/3 - 100, buttonsHeight); me.game.world.addChild(this.share, 12); //tweet button this.tweet = new Tweet(this.share.pos.x + 170, buttonsHeight); me.game.world.addChild(this.tweet, 12); //leaderboard button this.leader = new Leader(this.tweet.pos.x + 170, buttonsHeight); me.game.world.addChild(this.leader, 12); // add the dialog witht he game information if (game.data.newHiScore) { var newRect = new me.SpriteObject( 235, 355, me.loader.getImage('new') ); me.game.world.addChild(newRect, 12); } this.dialog = new (me.Renderable.extend({ // constructor init: function() { // size does not matter, it's just to avoid having a zero size // renderable this.parent(new me.Vector2d(), 100, 100); this.font = new me.Font('gamefont', 40, 'black', 'left'); this.steps = 'Steps: ' + game.data.steps.toString(); this.topSteps= 'Higher Step: ' + me.save.topSteps.toString(); }, update: function () { return true; }, draw: function (context) { var stepsText = this.font.measureText(context, this.steps); var topStepsText = this.font.measureText(context, this.topSteps); var scoreText = this.font.measureText(context, this.score); //steps this.font.draw( context, this.steps, me.game.viewport.width/2 - stepsText.width/2 - 60, me.game.viewport.height/2 ); //top score this.font.draw( context, this.topSteps, me.game.viewport.width/2 - stepsText.width/2 - 60, me.game.viewport.height/2 + 50 ); } })); me.game.world.addChild(this.dialog, 12); }, onDestroyEvent: function() { // unregister the event me.event.unsubscribe(this.handler); me.input.unbindKey(me.input.KEY.ENTER); me.input.unbindKey(me.input.KEY.SPACE); me.input.unbindPointer(me.input.mouse.LEFT); me.game.world.removeChild(this.ground); this.font = null; me.audio.stop("theme"); } });  Here is how the ScreenObjects works: First it calls the init constructor method for any variable initialization. onResetEvent is called next. This method will be called every time the scene is called. In our case the onResetEvent will add some objects to the game world stack. The onDestroyEvent acts like a garbage collector and unregisters bind events and removes some elements on the draw calls. Now, let's put it all together in the index.html file: <!DOCTYPE HTML> <html lang="en"> <head> <title>Clumsy Bird</title> </head> <body> <!-- the facebook init for the share button --> <div id="fb-root"></div> <script> window.fbAsyncInit = function() { FB.init({ appId : '213642148840283', status : true, xfbml : true }); }; (function(d, s, id){ var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/pt_BR/all.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); </script> <!-- Canvas placeholder --> <div id="screen"></div> <!-- melonJS Library --> <script type="text/javascript" src="lib/melonJS-1.0.2.js" ></script> <script type="text/javascript" src="js/entities/HUD.js" ></script> <script type="text/javascript" src="js/entities/entities.js" ></script> <script type="text/javascript" src="js/screens/title.js" ></script> <script type="text/javascript" src="js/screens/play.js" ></script> <script type="text/javascript" src="js/screens/gameover.js" ></script> </body> </html> Step 3 - Flying! To run our game we will need a web server of your choice. If you have Python installed, you can simply type the following in your shell: $python -m SimpleHTTPServer Then you can open your browser at http://localhost:8000. If all went well, you will see the title screen after it loads, like in the following image: I hope you enjoyed this post!  About this author Ellison Leão (@ellisonleao) is a passionate software engineer with more than 6 years of experience in web projects and is a contributor to the MelonJS framework and other open source projects. When he is not writing games, he loves to play drums.
Read more
  • 0
  • 13
  • 16577

article-image-interfacing-react-components-angular-applications
Patrick Marabeas
26 Sep 2014
10 min read
Save for later

Interfacing React Components with Angular Applications

Patrick Marabeas
26 Sep 2014
10 min read
There's been talk lately of using React as the view within Angular's MVC architecture. Angular, as we all know, uses dirty checking. As I'll touch on later, it accepts the fact of (minor) performance loss to gain the great two-way data binding it has. React, on the other hand, uses a virtual DOM and only renders the difference. This results in very fast performance. So, how do we leverage React's performance from our Angular application? Can we retain two-way data flow? And just how significant is the performance increase? The nrg module and demo code can be found over on my GitHub. The application To demonstrate communication between the two frameworks, let's build a reusable Angular module (nrg[Angular(ng) + React(r) = energy(nrg)!]) which will render (and re-render) a React component when our model changes. The React component will be composed of aninputandpelement that will display our model and will also update the model on change. To show this, we'll add aninputandpto our view bound to the model. In essence, changes to either input should result in all elements being kept in sync. We'll also add a button to our component that will demonstrate component unmounting on scope destruction. ;( ;(function(window, document, angular, undefined) { 'use strict'; angular.module('app', ['nrg']) .controller('MainController', ['$scope', function($scope) { $scope.text = 'This is set in Angular'; $scope.destroy = function() { $scope.$destroy(); } }]); })(window, document, angular); data-component specifies the React component we want to mount.data-ctrl (optional) specifies the controller we want to inject into the directive—this will allow specific components to be accessible onscope itself rather than scope.$parent.data-ng-model is the model we are going to pass between our Angular controller and our React view. <div data-ng-controller="MainController"> <!-- React component --> <div data-component="reactComponent" data-ctrl="" data-ng-model="text"> <!-- <input /> --> <!-- <button></button> --> <!-- <p></p> --> </div> <!-- Angular view --> <input type="text" data-ng-model="text" /> <p>{{text}}</p> </div> As you can see, the view has meaning when using Angular to render React components.<div data-component="reactComponent" data-ctrl="" data-ng-model="text"></div> has meaning when compared to<div id="reactComponent"></div>,which requires referencing a script file to see what component (and settings) will be mounted on that element. The Angular module - nrg.js The main functions of this reusable Angular module will be to: Specify the DOM element that the component should be mounted onto. Render the React component when changes have been made to the model. Pass the scope and element attributes to the component. Unmount the React component when the Angular scope is destroyed. The skeleton of our module looks like this: ;(function(window, document, angular, React, undefined) { 'use strict'; angular.module('nrg', []) To keep our code modular and extensible, we'll create a factory that will house our component functions, which are currently justrender and unmount . .factory('ComponentFactory', [function() { return { render: function() { }, unmount: function() { } } }]) This will be injected into our directive. .directive('component', ['$controller', 'ComponentFactory', function($controller, ComponentFactory) { return { restrict: 'EA', If a controller has been specified on the elements viadata-ctrl , then inject the$controller service. As mentioned earlier, this will allow scope variables and functions to be used within the React component to be accessible directly onscope , rather thanscope.$parent (the controller also doesn't need to be declared in the view withng-controller ). controller: function($scope, $element, $attrs){ return ($attrs.ctrl) ? $controller($attrs.ctrl, {$scope:$scope, $element:$element, $attrs:$attrs}) : null; }, Here’s an isolated scope with two-way-binding ondata-ng-model . scope: { ngModel: '=' }, link: function(scope, element, attrs) { // Calling ComponentFactory.render() & watching ng-model } } }]); })(window, document, angular, React); ComponentFactory Fleshing out theComponentFactory , we'll need to know how to render and unmount components. React.renderComponent( ReactComponent component, DOMElement container, [function callback] ) As such, we'll need to pass the component we wish to mount (component), the container we want to mount it in (element) and any properties (attrsandscope) we wish to pass to the component. This render function will be called every time the model is updated, so the updated scope will be pushed through each time. According to the React documentation, "If the React component was previously rendered into container, this (React.renderComponent) will perform an update on it and only mutate the DOM as necessary to reflect the latest React component." .factory('ComponentFactory', [function() { return { render: function(component, element, scope, attrs) { // If you have name-spaced your components, you'll want to specify that here - or pass it in via an attribute etc React.renderComponent(window[component]({ scope: scope, attrs: attrs }), element[0]); }, unmount: function(element) { React.unmountComponentAtNode(element[0]); } } }]) Component directive Back in our directive, we can now set up when we are going to call these two functions. link: function(scope, element, attrs) { // Collect the elements attrs in a nice usable object var attributes = {}; angular.forEach(element[0].attributes, function(a) { attributes[a.name.replace('data-','')] = a.value; }); // Render the component when the directive loads ComponentFactory.render(attrs.component, element, scope, attributes); // Watch the model and re-render the component scope.$watch('ngModel', function() { ComponentFactory.render(attrs.component, element, scope, attributes); }, true); // Unmount the component when the scope is destroyed scope.$on('$destroy', function () { ComponentFactory.unmount(element); }); } This implements dirty checking to see if the model has been updated. I haven't played around too much to see if there's a notable difference in performance between this and using a broadcast/listener. That said, to get a listener working as expected, you will need to wrap the render call in a $timeout to push it to the bottom of the stack to ensure scope is updated. scope.$on('renderMe', function() { $timeout(function() { ComponentFactory.render(attrs.component, element, scope, attributes); }); }); The React component We can now build our React component, which will use the model we defined as well as inform Angular of any updates it performs. /** @jsx React.DOM */ ;(function(window, document, React, undefined) { 'use strict'; window.reactComponent = React.createClass({ This is the content that will be rendered into the container. The properties that we passed to the component ({ scope: scope, attrs: attrs }) when we called React.renderComponent back in our component directive are now accessible via this.props. render: function(){ return ( <div> <input type='text' value={this.props.scope.ngModel} onChange={this.handleChange} /> <button onClick={this.deleteScope}>Destroy Scope</button> <p>{this.props.scope.ngModel}</p> </div> ) }, Via the on Change   event, we can call for Angular to run a digest, just as we normally would, but accessing scope via this.props : handleChange: function(event) { var _this = this; this.props.scope.$apply(function() { _this.props.scope.ngModel = event.target.value; }); }, Here we deal with the click event deleteScope  . The controller is accessible via scope.$parent  . If we had injected a controller into the component directive, its contents would be accessible directly on scope  , just as ngModel is.     deleteScope: function() { this.props.scope.$parent.destroy(); } }); })(window, document, React); The result Putting this code together (you can view the completed code on GitHub, or see it in action) we end up with: Two input elements, both of which update the model. Any changes in either our Angular application or our React view will be reflected in both. A React component button that calls a function in our MainController, destroying the scope and also resulting in the unmounting of the component. Pretty cool. But where is my perf increase!? This is obviously too small an application for anything to be gained by throwing your view over to React. To demonstrate just how much faster applications can be (by using React as the view), we'll throw a kitchen sink worth of randomly generated data at it. 5000 bits to be precise. Now, it should be stated that you probably have a pretty questionable UI if you have this much data binding going on. Misko Hevery has a great response regarding Angular's performance on StackOverflow. In summary: Humans are: Slow: Anything faster than 50ms is imperceptible to humans and thus can be considered as "instant". Limited: You can't really show more than about 2000 pieces of information to a human on a single page. Anything more than that is really bad UI, and humans can't process this anyway. Basically, know Angular's limits and your user's limits! That said, the following performance test was certainly accentuated on mobile devices. Though, on the flip side, UI should be simpler on mobile. Brute force performance demonstration ;(function(window, document, angular, undefined) { 'use strict'; angular.module('app') .controller('NumberController', ['$scope', function($scope) { $scope.numbers = []; ($scope.numGen = function(){ for(var i = 0; i < 5000; i++) { $scope.numbers[i] = Math.floor(Math.random() * (999999999999999 - 1)) + 1; } })(); }]); })(window, document, angular); Angular ng-repeat <div data-ng-controller="NumberController"> <button ng-click="numGen()">Refresh Data</button> <table> <tr ng-repeat="number in numbers"> <td>{{number}}</td> </tr> </table> </div> There was definitely lag felt as the numbers were loaded in and refreshed. From start to finish, this took around 1.5 seconds. React component <div data-ng-controller="NumberController"> <button ng-click="numGen()">Refresh Data</button> <div data-component="numberComponent" data-ng-model="numbers"></div> </div> ;(function(window, document, React, undefined) { window.numberComponent = React.createClass({ render: function() { var rows = this.props.scope.ngModel.map(function(number) { return ( <tr> <td>{number}</td> </tr> ); }); return ( <table>{rows}</table> ); } }); })(window, document, React); So that just happened. 270 milliseconds start to finish. Around 80% faster! Conclusion So, should you go rewrite all those Angular modules as React components? Probably not. It really comes down to the application you are developing and how dependent you are on OSS. It's definitely possible that a handful of complex modules could put your application in the realm of “feeling a tad sluggish”, but it should be remembered that perceived performance is all that matters to the user. Altering the manner in which content is loaded could end up being a better investment of time. Users will definitely feel performance increases on mobile websites sooner, however, and is certainly something to keep in mind. The nrg module and demo code can be found over on my GitHub. Visit our JavaScript page for more JavaScript content and tutorials!  About the author A guest post by Patrick Marabeas, a freelance frontend developer who loves learning and working with cutting edge web technologies. He spends much of his free time developing Angular modules, such as ng-FitText, ng-Slider, ng-YouTubeAPI, and ng-ScrollSpy. You can follow him on Twitter: @patrickmarabeas.
Read more
  • 0
  • 0
  • 10953
article-image-machine-learning-ipython-scikit-learn-0
Packt
25 Sep 2014
13 min read
Save for later

Machine Learning in IPython with scikit-learn

Packt
25 Sep 2014
13 min read
This article written by Daniele Teti, the author of Ipython Interactive Computing and Visualization Cookbook, the basics of the machine learning scikit-learn package (http://scikit-learn.org) is introduced. Its clean API makes it really easy to define, train, and test models. Plus, scikit-learn is specifically designed for speed and (relatively) big data. (For more resources related to this topic, see here.) A very basic example of linear regression in the context of curve fitting is shown here. This toy example will allow to illustrate key concepts such as linear models, overfitting, underfitting, regularization, and cross-validation. Getting ready You can find all instructions to install scikit-learn on the main documentation. For more information, refer to http://scikit-learn.org/stable/install.html. With anaconda, you can type conda install scikit-learn in a terminal. How to do it... We will generate a one-dimensional dataset with a simple model (including some noise), and we will try to fit a function to this data. With this function, we can predict values on new data points. This is a curve fitting regression problem. First, let's make all the necessary imports: In [1]: import numpy as np        import scipy.stats as st        import sklearn.linear_model as lm        import matplotlib.pyplot as plt        %matplotlib inline We now define a deterministic nonlinear function underlying our generative model: In [2]: f = lambda x: np.exp(3 * x) We generate the values along the curve on [0,2]: In [3]: x_tr = np.linspace(0., 2, 200)        y_tr = f(x_tr) Now, let's generate data points within [0,1]. We use the function f and we add some Gaussian noise: In [4]: x = np.array([0, .1, .2, .5, .8, .9, 1])        y = f(x) + np.random.randn(len(x)) Let's plot our data points on [0,1]: In [5]: plt.plot(x_tr[:100], y_tr[:100], '--k')        plt.plot(x, y, 'ok', ms=10) In the image, the dotted curve represents the generative model. Now, we use scikit-learn to fit a linear model to the data. There are three steps. First, we create the model (an instance of the LinearRegression class). Then, we fit the model to our data. Finally, we predict values from our trained model. In [6]: # We create the model.        lr = lm.LinearRegression()        # We train the model on our training dataset.        lr.fit(x[:, np.newaxis], y)        # Now, we predict points with our trained model.        y_lr = lr.predict(x_tr[:, np.newaxis]) We need to convert x and x_tr to column vectors, as it is a general convention in scikit-learn that observations are rows, while features are columns. Here, we have seven observations with one feature. We now plot the result of the trained linear model. We obtain a regression line in green here: In [7]: plt.plot(x_tr, y_tr, '--k')        plt.plot(x_tr, y_lr, 'g')        plt.plot(x, y, 'ok', ms=10)        plt.xlim(0, 1)        plt.ylim(y.min()-1, y.max()+1)        plt.title("Linear regression") The linear fit is not well-adapted here, as the data points are generated according to a nonlinear model (an exponential curve). Therefore, we are now going to fit a nonlinear model. More precisely, we will fit a polynomial function to our data points. We can still use linear regression for this, by precomputing the exponents of our data points. This is done by generating a Vandermonde matrix, using the np.vander function. We will explain this trick in How it works…. In the following code, we perform and plot the fit: In [8]: lrp = lm.LinearRegression()        plt.plot(x_tr, y_tr, '--k')        for deg in [2, 5]:            lrp.fit(np.vander(x, deg + 1), y)            y_lrp = lrp.predict(np.vander(x_tr, deg + 1))            plt.plot(x_tr, y_lrp,                      label='degree ' + str(deg))             plt.legend(loc=2)            plt.xlim(0, 1.4)            plt.ylim(-10, 40)            # Print the model's coefficients.            print(' '.join(['%.2f' % c for c in                            lrp.coef_]))        plt.plot(x, y, 'ok', ms=10)      plt.title("Linear regression") 25.00 -8.57 0.00 -132.71 296.80 -211.76 72.80 -8.68 0.00 We have fitted two polynomial models of degree 2 and 5. The degree 2 polynomial appears to fit the data points less precisely than the degree 5 polynomial. However, it seems more robust; the degree 5 polynomial seems really bad at predicting values outside the data points (look for example at the x 1 portion). This is what we call overfitting; by using a too complex model, we obtain a better fit on the trained dataset, but a less robust model outside this set. Note the large coefficients of the degree 5 polynomial; this is generally a sign of overfitting. We will now use a different learning model, called ridge regression. It works like linear regression except that it prevents the polynomial's coefficients from becoming too big. This is what happened in the previous example. By adding a regularization term in the loss function, ridge regression imposes some structure on the underlying model. We will see more details in the next section. The ridge regression model has a meta-parameter, which represents the weight of the regularization term. We could try different values with trials and errors, using the Ridge class. However, scikit-learn includes another model called RidgeCV, which includes a parameter search with cross-validation. In practice, it means that we don't have to tweak this parameter by hand—scikit-learn does it for us. As the models of scikit-learn always follow the fit-predict API, all we have to do is replace lm.LinearRegression() by lm.RidgeCV() in the previous code. We will give more details in the next section. In [9]: ridge = lm.RidgeCV()        plt.plot(x_tr, y_tr, '--k')               for deg in [2, 5]:             ridge.fit(np.vander(x, deg + 1), y);            y_ridge = ridge.predict(np.vander(x_tr, deg+1))            plt.plot(x_tr, y_ridge,                      label='degree ' + str(deg))            plt.legend(loc=2)            plt.xlim(0, 1.5)           plt.ylim(-5, 80)            # Print the model's coefficients.            print(' '.join(['%.2f' % c                            for c in ridge.coef_]))               plt.plot(x, y, 'ok', ms=10)        plt.title("Ridge regression") 11.36 4.61 0.00 2.84 3.54 4.09 4.14 2.67 0.00 This time, the degree 5 polynomial seems more precise than the simpler degree 2 polynomial (which now causes underfitting). Ridge regression reduces the overfitting issue here. Observe how the degree 5 polynomial's coefficients are much smaller than in the previous example. How it works... In this section, we explain all the aspects covered in this article. The scikit-learn API scikit-learn implements a clean and coherent API for supervised and unsupervised learning. Our data points should be stored in a (N,D) matrix X, where N is the number of observations and D is the number of features. In other words, each row is an observation. The first step in a machine learning task is to define what the matrix X is exactly. In a supervised learning setup, we also have a target, an N-long vector y with a scalar value for each observation. This value is continuous or discrete, depending on whether we have a regression or classification problem, respectively. In scikit-learn, models are implemented in classes that have the fit() and predict() methods. The fit() method accepts the data matrix X as input, and y as well for supervised learning models. This method trains the model on the given data. The predict() method also takes data points as input (as a (M,D) matrix). It returns the labels or transformed points as predicted by the trained model. Ordinary least squares regression Ordinary least squares regression is one of the simplest regression methods. It consists of approaching the output values yi with a linear combination of Xij: Here, w = (w1, ..., wD) is the (unknown) parameter vector. Also, represents the model's output. We want this vector to match the data points y as closely as possible. Of course, the exact equality cannot hold in general (there is always some noise and uncertainty—models are always idealizations of reality). Therefore, we want to minimize the difference between these two vectors. The ordinary least squares regression method consists of minimizing the following loss function: This sum of the components squared is called the L2 norm. It is convenient because it leads to differentiable loss functions so that gradients can be computed and common optimization procedures can be performed. Polynomial interpolation with linear regression Ordinary least squares regression fits a linear model to the data. The model is linear both in the data points Xiand in the parameters wj. In our example, we obtain a poor fit because the data points were generated according to a nonlinear generative model (an exponential function). However, we can still use the linear regression method with a model that is linear in wj, but nonlinear in xi. To do this, we need to increase the number of dimensions in our dataset by using a basis of polynomial functions. In other words, we consider the following data points: Here, D is the maximum degree. The input matrix X is therefore the Vandermonde matrix associated to the original data points xi. For more information on the Vandermonde matrix, refer to http://en.wikipedia.org/wiki/Vandermonde_matrix. Here, it is easy to see that training a linear model on these new data points is equivalent to training a polynomial model on the original data points. Ridge regression Polynomial interpolation with linear regression can lead to overfitting if the degree of the polynomials is too large. By capturing the random fluctuations (noise) instead of the general trend of the data, the model loses some of its predictive power. This corresponds to a divergence of the polynomial's coefficients wj. A solution to this problem is to prevent these coefficients from growing unboundedly. With ridge regression (also known as Tikhonov regularization) this is done by adding a regularization term to the loss function. For more details on Tikhonov regularization, refer to http://en.wikipedia.org/wiki/Tikhonov_regularization. By minimizing this loss function, we not only minimize the error between the model and the data (first term, related to the bias), but also the size of the model's coefficients (second term, related to the variance). The bias-variance trade-off is quantified by the hyperparameter , which specifies the relative weight between the two terms in the loss function. Here, ridge regression led to a polynomial with smaller coefficients, and thus a better fit. Cross-validation and grid search A drawback of the ridge regression model compared to the ordinary least squares model is the presence of an extra hyperparameter . The quality of the prediction depends on the choice of this parameter. One possibility would be to fine-tune this parameter manually, but this procedure can be tedious and can also lead to overfitting problems. To solve this problem, we can use a grid search; we loop over many possible values for , and we evaluate the performance of the model for each possible value. Then, we choose the parameter that yields the best performance. How can we assess the performance of a model with a given value? A common solution is to use cross-validation. This procedure consists of splitting the dataset into a train set and a test set. We fit the model on the train set, and we test its predictive performance on the test set. By testing the model on a different dataset than the one used for training, we reduce overfitting. There are many ways to split the initial dataset into two parts like this. One possibility is to remove one sample to form the train set and to put this one sample into the test set. This is called Leave-One-Out cross-validation. With N samples, we obtain N sets of train and test sets. The cross-validated performance is the average performance on all these set decompositions. As we will see later, scikit-learn implements several easy-to-use functions to do cross-validation and grid search. In this article, there exists a special estimator called RidgeCV that implements a cross-validation and grid search procedure that is specific to the ridge regression model. Using this model ensures that the best hyperparameter is found automatically for us. There's more… Here are a few references about least squares: Ordinary least squares on Wikipedia, available at http://en.wikipedia.org/wiki/Ordinary_least_squares Linear least squares on Wikipedia, available at http://en.wikipedia.org/wiki/Linear_least_squares_(mathematics) Here are a few references about cross-validation and grid search: Cross-validation in scikit-learn's documentation, available at http://scikit-learn.org/stable/modules/cross_validation.html Grid search in scikit-learn's documentation, available at http://scikit-learn.org/stable/modules/grid_search.html Cross-validation on Wikipedia, available at http://en.wikipedia.org/wiki/Cross-validation_%28statistics%29 Here are a few references about scikit-learn: scikit-learn basic tutorial available at http://scikit-learn.org/stable/tutorial/basic/tutorial.html scikit-learn tutorial given at the SciPy 2013 conference available at https://github.com/jakevdp/sklearn_scipy2013 Summary Using the scikit-learn Python package, this article illustrates fundamental data mining and machine learning concepts such as supervised and unsupervised learning, classification, regression, feature selection, feature extraction, overfitting, regularization, cross-validation, and grid search. Resources for Article: Further resources on this subject: Driving Visual Analyses with Automobile Data (Python) [Article] Fast Array Operations with NumPy [Article] Python 3: Designing a Tasklist Application [Article]
Read more
  • 0
  • 0
  • 3237

article-image-getting-started-with-vagrant
Timothy Messier
25 Sep 2014
6 min read
Save for later

Getting started with Vagrant

Timothy Messier
25 Sep 2014
6 min read
As developers, one of the most frustrating types of bugs are those that only happen in production. Continuous integration servers have gone a long way in helping prevent these types of configuration bugs, but wouldn't it be nice to avoid these bugs altogether? Developers’ machines tend to be a mess of software. Multiple versions of languages, random services with manually tweaked configs, and debugging tools all contribute to a cluttered and unpredictable environment. Sandboxes Many developers are familiar with sandboxing development. Tools like virtualenv and rvm were created to install packages in isolated environments, allowing for multiple versions of packages to be installed on the same machine. Newer languages like nodejs install packages in a sandbox by default. These are very useful tools for managing package versions in both development and production, but they also lead to some of the aforementioned clutter. Additionally, these tools are specific to particular languages. They do not allow for easily installing multiple versions of services like databases. Luckily, there is Vagrant to address all these issues (and a few more). Getting started Vagrant at its core is simply a wrapper around virtual machines. One of Vagrant's strengths is its ease of use. With just a few commands, virtual machines can be created, provisioned, and destroyed. First grab the latest download for your OS from Vagrant's download page (https://www.vagrantup.com/downloads.html). NOTE: For Linux users, although your distro may have a version of Vagrant via its package manager, it is most likely outdated. You probably want to use the version from the link above to get the latest features. Also install Virtualbox (https://www.virtualbox.org/wiki/Downloads) using your preferred method. Vagrant supports other virtualization providers as well as docker, but Virtualbox is the easiest to get started with. When most Vagrant commands are run, they will look for a file named Vagrantfile (or vagrantfile) in the current directory. All configuration for Vagrant is done in this file and it is isolated to a particular Vagrant instance. Create a new Vagrantfile: $ vagrant init hashicorp/precise64 This creates a Vagrantfile using the base virtual image, hashicorp/precise64, which is a stock Ubuntu 12.04 image provided by Hashicorp. This file contains many useful comments about the various configurations that can be done. If this command is run with the --minimal flag, it will create a file without comments, like the following: # -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" end Now create the virtual machine: $ vagrant up This isn’t too difficult. You now have a nice clean machine in just two commands. What did Vagrant up do? First, if you did not already have the base box, hashicorp/precise64, it was downloaded from Vagrant Cloud. Then, Vagrant made a new machine based on the current directory name and a unique number. Once the machine was booted it created a shared folder between the host's current directory and the guest's /vagrant. To access the new machine run: $ vagrant ssh Provisioning At this point, additional software needs to be installed. While a clean Ubuntu install is nice, it does not have the software to develop or run many applications. While it may be tempting to just start installing services and libraries with apt-get, that would just start to cause the same old clutter. Instead, use Vagrant's provisioning infrastructure. Vagrant has support for all of the major provision tools like Salt (http://www.saltstack.com/), Chef (http://www.getchef.com/chef/) or Ansible (http://www.ansible.com/home). It also supports calling shell commands. For the sake of keeping this post focused on Vagrant, an example using the shell provisioner will be used. However, to unleash the full power of Vagrant, use the same provisioner used for production systems. This will enable the virtual machine to be configured using the same provisioning steps as production, thus ensuring that the virtual machine mirrors the production environment. To continue this example, add a provision section to the Vagrantfile: # -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" config.vm.provision "shell", path: "provision.sh" end This tells Vagrant to run the script provision.sh. Create this file with some install commands: #!/bin/bash # Install nginx apt-get -y install nginx Now tell Vagrant to provision the machine: $ vagrant provision NOTE: When running Vagrant for the first time, Vagrant provision is automatically called. To force a provision, call vagrant up --provision. The virtual machine should now have nginx installed with the default page being served. To access this page from the host, port forwarding can be set in the Vagrantfile: # -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" config.vm.network "forwarded_port", guest: 80, host: 8080 config.vm.provision "shell", path: "provision.sh" end For the forwarding to take effect, the virtual machine must be restarted: $ vagrant halt && vagrant up Now in a browser go to http://localhost:8080 to see the nginx welcome page. Next steps This simple example shows how easy Vagrant is to use, and how quickly machines can be created and configured. However, to truly battle issues like "it works on my machine", Vagrant needs to leverage the same provisioning tools that are used for production servers. If these provisioning scripts are cleanly implemented, Vagrant can easily leverage them and make creating a pristine production-like environment easy. Since all configuration is contained in a single file, it becomes simple to include a Vagrant configuration along with code in a repository. This allows developers to easily create identical environments for testing code. Final notes When evaluating any open source tool, at some point you need to examine the health of the community supporting the tool. In the case of Vagrant, the community seems very healthy. The primary developer continues to be responsive about bugs and improvements, despite having launched a new company in the wake of Vagrant's success. New features continue to roll in, all the while keeping a very stable product. All of this is good news since there seem to be no other tools that make creating clean sandbox environments as effortless as Vagrant. About the author Timothy Messier is involved in many open source devops projects, including Vagrant and Salt, and can be contacted at tim.messier@gmail.com.
Read more
  • 0
  • 0
  • 9978

article-image-building-content-management-system
Packt
25 Sep 2014
25 min read
Save for later

Building a Content Management System

Packt
25 Sep 2014
25 min read
In this article by Charles R. Portwood II, the author of Yii Project Blueprints, we will look at how to create a feature-complete content management system and blogging platform. (For more resources related to this topic, see here.) Describing the project Our CMS can be broken down into several different components: Users who will be responsible for viewing and managing the content Content to be managed Categories for our content to be placed into Metadata to help us further define our content and users Search engine optimizations Users The first component of our application is the users who will perform all the tasks in our application. For this application, we're going to largely reuse the user database and authentication system. In this article, we'll enhance this functionality by allowing social authentication. Our CMS will allow users to register new accounts from the data provided by Twitter; after they have registered, the CMS will allow them to sign-in to our application by signing in to Twitter. To enable us to know if a user is a socially authenticated user, we have to make several changes to both our database and our authentication scheme. First, we're going to need a way to indicate whether a user is a socially authenticated user. Rather than hardcoding a isAuthenticatedViaTwitter column in our database, we'll create a new database table called user_metadata, which will be a simple table that contains the user's ID, a unique key, and a value. This will allow us to store additional information about our users without having to explicitly change our user's database table every time we want to make a change: ID INTEGER PRIMARY KEYuser_id INTEGERkey STRINGvalue STRINGcreated INTEGERupdated INTEGER We'll also need to modify our UserIdentity class to allow socially authenticated users to sign in. To do this, we'll be expanding upon this class to create a RemoteUserIdentity class that will work off the OAuth codes that Twitter (or any other third-party source that works with HybridAuth) provide to us rather than authenticating against a username and password. Content At the core of our CMS is our content that we'll manage. For this project, we'll manage simple blog posts that can have additional metadata associated with them. Each post will have a title, a body, an author, a category, a unique URI or slug, and an indication whether it has been published or not. Our database structure for this table will look as follows: ID INTEGER PRIMARY KEYtitle STRINGbody TEXTpublished INTEGERauthor_id INTEGERcategory_id INTEGERslug STRINGcreated INTEGERupdated INTEGER Each post will also have one or many metadata columns that will further describe the posts we'll be creating. We can use this table (we’ll call it content_metadata) to have our system store information about each post automatically for us, or add information to our posts ourselves, thereby eliminating the need to constantly migrate our database every time we want to add a new attribute to our content: ID INTEGER PRIMARY KEYcontent_id INTEGERkey STRINGvalue STRINGcreated INTEGERupdated INTEGER Categories Each post will be associated with a category in our system. These categories will help us further refine our posts. As with our content, each category will have its own slug. Before either a post or a category is saved, we'll need to verify that the slug is not already in use. Our table structure will look as follows: ID INTEGER PRIMARY KEYname STRINGdescription TEXTslug STRINGcreated INTEGERupdated INTEGER Search engine optimizations The last core component of our application is optimization for search engines so that our content can be indexed quickly. SEO is important because it increases our discoverability and availability both on search engines and on other marketing materials. In our application, there are a couple of things we'll perform to improve our SEO: The first SEO enhancement we'll add is a sitemap.xml file, which we can submit to popular search engines to index. Rather than crawl our content, search engines can very quickly index our sitemap.xml file, which means that our content will show up in search engines faster. The second enhancement we'll be adding is the slugs that we discussed earlier. Slugs allow us to indicate what a particular post is about directly from a URL. So rather than have a URL that looks like http://chapter6.example.com/content/post/id/5, we can have URL's that look like: http://chapter6.example.com/my-awesome-article. These types of URLs allow search engines and our users to know what our content is about without even looking at the content itself, such as when a user is browsing through their bookmarks or browsing a search engine. Initializing the project To provide us with a common starting ground, a skeleton project has been included with the project resources for this article. Included with this skeleton project are the necessary migrations, data files, controllers, and views to get us started with developing. Also included in this skeleton project are the user authentication classes. Copy this skeleton project to your web server, configure it so that it responds to chapter6.example.com as outlined at the beginning of the article, and then perform the following steps to make sure everything is set up: Adjust the permissions on the assets and protected/runtime folders so that they are writable by your web server. In this article, we'll once again use the latest version of MySQL (at the time of writing MySQL 5.6). Make sure that your MySQL server is set up and running on your server. Then, create a username, password, and database for our project to use, and update your protected/config/main.php file accordingly. For simplicity, you can use ch6_cms for each value. Install our Composer dependencies: Composer install Run the migrate command and install our mock data: php protected/yiic.php migrate up --interactive=0psql ch6_cms -f protected/data/postgres.sql Finally, add your SendGrid credentials to your protected/config/params.php file: 'username' => '<username>','password' => '<password>','from' => 'noreply@ch6.home.erianna.net') If everything is loaded correctly, you should see a 404 page similar to the following: Exploring the skeleton project There are actually a lot of different things going on in the background to make this work even if this is just a 404 error. Before we start doing any development, let's take a look at a few of the classes that have been provided in our skeleton project in the protected/components folder. Extending models from a common class The first class that has been provided to us is an ActiveRecord extension called CMSActiveRecord that all of our models will stem from. This class allows us to reduce the amount of code that we have to write in each class. For now, we'll simply add CTimestampBehavior and the afterFind() method to store the old attributes for the time the need arises to compare the changed attributes with the new attributes: class CMSActiveRecordCMSActiveRecord extends CActiveRecord{public $_oldAttributes = array();public function behaviors(){return array('CTimestampBehavior' => array('class' => 'zii.behaviors.CTimestampBehavior','createAttribute' => 'created','updateAttribute' => 'updated','setUpdateOnCreate' => true));}public function afterFind(){if ($this !== NULL)$this->_oldAttributes = $this->attributes;return parent::afterFind();}} Creating a custom validator for slugs Since both Content and Category classes have slugs, we'll need to add a custom validator to each class that will enable us to ensure that the slug is not already in use by either a post or a category. To do this, we have another class called CMSSlugActiveRecord that extends CMSActiveRecord with a validateSlug() method that we'll implement as follows: class CMSSLugActiveRecord extends CMSActiveRecord{public function validateSlug($attributes, $params){// Fetch any records that have that slug$content = Content::model()->findByAttributes(array('slug' =>$this->slug));$category = Category::model()->findByAttributes(array('slug' =>$this->slug));$class = strtolower(get_class($this));if ($content == NULL && $category == NULL)return true;else if (($content == NULL && $category != NULL) || ($content !=NULL && $category == NULL)){$this->addError('slug', 'That slug is already in use');return false;}else{if ($this->id == $$class->id)return true;}$this->addError('slug', 'That slug is already in use');return false;}} This implementation simply checks the database for any item that has that slug. If nothing is found, or if the current item is the item that is being modified, then the validator will return true. Otherwise, it will add an error to the slug attribute and return false. Both our Content model and Category model will extend from this class. View management with themes One of the largest challenges of working with larger applications is changing their appearance without locking functionality into our views. One way to further separate our business logic from our presentation logic is to use themes. Using themes in Yii, we can dynamically change the presentation layer of our application simply utilizing the Yii::app()->setTheme('themename) method. Once this method is called, Yii will look for view files in themes/themename/views rather than protected/views. Throughout the rest of the article, we'll be adding views to a custom theme called main, which is located in the themes folder. To set this theme globally, we'll be creating a custom class called CMSController, which all of our controllers will extend from. For now, our theme name will be hardcoded within our application. This value could easily be retrieved from a database though, allowing us to dynamically change themes from a cached or database value rather than changing it in our controller. Have a look at the following lines of code: class CMSController extends CController{public function beforeAction($action){Yii::app()->setTheme('main');return parent::beforeAction($action);}} Truly dynamic routing In our previous applications, we had long, boring URL's that had lots of IDs and parameters in them. These URLs provided a terrible user experience and prevented search engines and users from knowing what the content was about at a glance, which in turn would hurt our SEO rankings on many search engines. To get around this, we're going to heavily modify our UrlManager class to allow truly dynamic routing, which means that, every time we create or update a post or a category, our URL rules will be updated. Telling Yii to use our custom UrlManager Before we can start working on our controllers, we need to create a custom UrlManager to handle routing of our content so that we can access our content by its slug. The steps are as follows: The first change we need to make to allow for this routing is to update the components section of our protected/config/main.php file. This will tell Yii what class to use for the UrlManager component: 'urlManager' => array('class' => 'application.components.CMSUrlManager','urlFormat' => 'path','showScriptName' => false) Next, within our protected/components folder, we need to create CMSUrlManager.php: class CMSUrlManager extends CUrlManager {} CUrlManager works by populating a rules array. When Yii is bootstrapped, it will trigger the processRules() method to determine which route should be executed. We can overload this method to inject our own rules, which will ensure that the action that we want to be executed is executed. To get started, let's first define a set of default routes that we want loaded. The routes defined in the following code snippet will allow for pagination on our search and home page, enable a static path for our sitemap.xml file, and provide a route for HybridAuth to use for social authentication: public $defaultRules = array('/sitemap.xml' => '/content/sitemap','/search/<page:d+>' => '/content/search','/search' => '/content/search','/blog/<page:d+>' => '/content/index','/blog' => '/content/index','/' => '/content/index','/hybrid/<provider:w+>' => '/hybrid/index',); Then, we'll implement our processRules() method: protected function processRules() {} CUrlManager already has a public property that we can interface to modify the rules, so we'll inject our own rules into this. The rules property is the same property that can be accessed from within our config file. Since processRules() gets called on every page load, we'll also utilize caching so that our rules don't have to be generated every time. We'll start by trying to load any of our pregenerated rules from our cache, depending upon whether we are in debug mode or not: $this->rules = !YII_DEBUG ? Yii::app()->cache->get('Routes') : array(); If the rules we get back are already set up, we'll simple return them; otherwise, we'll generate the rules, put them into our cache, and then append our basic URL rules: if ($this->rules == false || empty($this->rules)) { $this->rules = array(); $this->rules = $this->generateClientRules(); $this->rules = CMap::mergearray($this->addRssRules(), $this- >rules); Yii::app()->cache->set('Routes', $this->rules); } $this->rules['<controller:w+>/<action:w+>/<id:w+>'] = '/'; $this->rules['<controller:w+>/<action:w+>'] = '/'; return parent::processRules(); For abstraction purposes, within our processRules() method, we've utilized two methods we'll need to create: generateClientRules, which will generate the rules for content and categories, and addRSSRules, which will generate the RSS routes for each category. The first method, generateClientRules(), simply loads our default rules that we defined earlier with the rules generated from our content and categories, which are populated by the generateRules() method: private function generateClientRules() { $rules = CMap::mergeArray($this->defaultRules, $this->rules); return CMap::mergeArray($this->generateRules(), $rules); } private function generateRules() { return CMap::mergeArray($this->generateContentRules(), $this- >generateCategoryRules()); } The generateRules() method, that we just defined, actually calls the methods that build our routes. Each route is a key-value pair that will take the following form: array( '<slug>' => '<controller>/<action>/id/<id>' ) Content rules will consist of an entry that is published. Have a look at the following code: private function generateContentRules(){$rules = array();$criteria = new CDbCriteria;$criteria->addCondition('published = 1');$content = Content::model()->findAll($criteria);foreach ($content as $el){if ($el->slug == NULL)continue;$pageRule = $el->slug.'/<page:d+>';$rule = $el->slug;if ($el->slug == '/')$pageRule = $rule = '';$pageRule = $el->slug . '/<page:d+>';$rule = $el->slug;$rules[$pageRule] = "content/view/id/{$el->id}";$rules[$rule] = "content/view/id/{$el->id}";}return $rules;} Our category rules will consist of all categories in our database. Have a look at the following code: private function generateCategoryRules() { $rules = array(); $categories = Category::model()->findAll(); foreach ($categories as $el) { if ($el->slug == NULL) continue; $pageRule = $el->slug.'/<page:d+>'; $rule = $el->slug; if ($el->slug == '/') $pageRule = $rule = ''; $pageRule = $el->slug . '/<page:d+>'; $rule = $el->slug; $rules[$pageRule] = "category/index/id/{$el->id}"; $rules[$rule] = "category/index/id/{$el->id}"; } return $rules; } Finally, we'll add our RSS rules that will allow RSS readers to read all content for the entire site or for a particular category, as follows: private function addRSSRules() { $categories = Category::model()->findAll(); foreach ($categories as $category) $routes[$category->slug.'.rss'] = "category/rss/id/ {$category->id}"; $routes['blog.rss'] = '/category/rss'; return $routes; } Displaying and managing content Now that Yii knows how to route our content, we can begin work on displaying and managing it. Begin by creating a new controller called ContentController in protected/controllers that extends CMSController. Have a look at the following line of code: class ContentController extends CMSController {} To start with, we'll define our accessRules() method and the default layout that we're going to use. Here's how: public $layout = 'default';public function filters(){return array('accessControl',);}public function accessRules(){return array(array('allow','actions' => array('index', 'view', 'search'),'users' => array('*')),array('allow','actions' => array('admin', 'save', 'delete'),'users'=>array('@'),'expression' => 'Yii::app()->user->role==2'),array('deny', // deny all users'users'=>array('*'),),);} Rendering the sitemap The first method we'll be implementing is our sitemap action. In our ContentController, create a new method called actionSitemap(): public function actionSitemap() {} The steps to be performed are as follows: Since sitemaps come in XML formatting, we'll start by disabling WebLogRoute defined in our protected/config/main.php file. This will ensure that our XML validates when search engines attempt to index it: Yii::app()->log->routes[0]->enabled = false; We'll then send the appropriate XML headers, disable the rendering of the layout, and flush any content that may have been queued to be sent to the browser: ob_end_clean();header('Content-type: text/xml; charset=utf-8');$this->layout = false; Then, we'll load all the published entries and categories and send them to our sitemap view: $content = Content::model()->findAllByAttributes(array('published'=> 1));$categories = Category::model()->findAll();$this->renderPartial('sitemap', array('content' => $content,'categories' => $categories,'url' => 'http://'.Yii::app()->request->serverName .Yii::app()->baseUrl)) Finally, we have two options to render this view. We can either make it a part of our theme in themes/main/views/content/sitemap.php, or we can place it in protected/views/content/sitemap.php. Since a sitemap's structure is unlikely to change, let's put it in the protected/views folder: <?php echo '<?xml version="1.0" encoding="UTF-8"?>'; ?><urlset ><?php foreach ($content as $v): ?><url><loc><?php echo $url .'/'. htmlspecialchars(str_replace('/', '', $v['slug']), ENT_QUOTES, "utf-8"); ?></loc><lastmod><?php echo date('c',strtotime($v['updated']));?></lastmod><changefreq>weekly</changefreq><priority>1</priority></url><?php endforeach; ?><?php foreach ($categories as $v): ?><url><loc><?php echo $url .'/'. htmlspecialchars(str_replace('/', '', $v['slug']), ENT_QUOTES, "utf-8"); ?></loc><lastmod><?php echo date('c',strtotime($v['updated']));?></lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><?php endforeach; ?></urlset> You can now load http://chapter6.example.com/sitemap.xml in your browser to see the sitemap. Before you make your site live, be sure to submit this file to search engines for them to index. Displaying a list view of content Next, we'll implement the actions necessary to display all of our content and a particular post. We'll start by providing a paginated view of our posts. Since CListView and the Content model's search() method already provide this functionality, we can utilize those classes to generate and display this data: To begin with, open protected/models/Content.php and modify the return value of the search() method as follows. This will ensure that Yii's pagination uses the correct variable in our CListView, and tells Yii how many results to load per page. return new CActiveDataProvider($this, array('criteria' =>$criteria,'pagination' => array('pageSize' => 5,'pageVar' =>'page'))); Next, implement the actionIndex() method with the $page parameter. We've already told our UrlManager how to handle this, which means that we'll get pretty URI's for pagination (for example, /blog, /blog/2, /blog/3, and so on): public function actionIndex($page=1){// Model Search without $_GET params$model = new Content('search');$model->unsetAttributes();$model->published = 1;$this->render('//content/all', array('dataprovider' => $model->search()));} Then we'll create a view in themes/main/views/content/all.php, that will display the data within our dataProvider: <?php $this->widget('zii.widgets.CListView', array('dataProvider'=>$dataprovider,'itemView'=>'//content/list','summaryText' => '','pager' => array('htmlOptions' => array('class' => 'pager'),'header' => '','firstPageCssClass'=>'hide','lastPageCssClass'=>'hide','maxButtonCount' => 0))); Finally, copy themes/main/views/content/all.php from the project resources folder so that our views can render. Since our database has already been populated with some sample data, you can start playing around with the results right away, as shown in the following screenshot: Displaying content by ID Since our routing rules are already set up, displaying our content is extremely simple. All that we have to do is search for a published model with the ID passed to the view action and render it: public function actionView($id=NULL){// Retrieve the data$content = Content::model()->findByPk($id);// beforeViewAction should catch thisif ($content == NULL || !$content->published)throw new CHttpException(404, 'The article you specified doesnot exist.');$this->render('view', array('id' => $id,'post' => $content));} After copying themes/main/views/content/view.php from the project resources folder into your project, you'll be able to click into a particular post from the home page. In its actions present form, this action has introduced an interesting side effect that could negatively impact our SEO rankings on search engines—the same entry can now be accessed from two URI's. For example, http://chapter6.example.com/content/view/id/1 and http://chapter6.example.com/quis-condimentum-tortor now bring up the same post. Fortunately, correcting this bug is fairly easy. Since the goal of our slugs is to provide more descriptive URI's, we'll simply block access to the view if a user tries to access it from the non-slugged URI. We'll do this by creating a new method called beforeViewAction() that takes the entry ID as a parameter and gets called right after the actionView() method is called. This private method will simply check the URI from CHttpRequest to determine how actionView was accessed and return a 404 if it's not through our beautiful slugs: private function beforeViewAction($id=NULL){// If we do not have an ID, consider it to be null, and throw a 404errorif ($id == NULL)throw new CHttpException(404,'The specified post cannot befound.');// Retrieve the HTTP Request$r = new CHttpRequest();// Retrieve what the actual URI$requestUri = str_replace($r->baseUrl, '', $r->requestUri);// Retrieve the route$route = '/' . $this->getRoute() . '/' . $id;$requestUri = preg_replace('/?(.*)/','',$requestUri);// If the route and the uri are the same, then a direct accessattempt was made, and we need to block access to the controllerif ($requestUri == $route)throw new CHttpException(404, 'The requested post cannot befound.');return str_replace($r->baseUrl, '', $r->requestUri);} Then right after our actionView starts, we can simultaneously set the correct return URL and block access to the content if it wasn't accessed through the slug as follows: Yii::app()->user->setReturnUrl($this->beforeViewAction($id)); Adding comments to our CMS with Disqus Presently, our content is only informative in nature—we have no way for our users to communicate with us what they thought about our entry. To encourage engagement, we can add a commenting system to our CMS to further engage with our readers. Rather than writing our own commenting system, we can leverage comment through Disqus, a free, third-party commenting system. Even through Disqus, comments are implemented in JavaScript and we can create a custom widget wrapper for it to display comments on our site. The steps are as follows: To begin with, log in to the Disqus account you created at the beginning of this article as outlined in the prerequisites section. Then, navigate to http://disqus.com/admin/create/ and fill out the form fields as prompted and as shown in the following screenshot: Then, add a disqus section to your protected/config/params.php file with your site shortname: 'disqus' => array('shortname' => 'ch6disqusexample',) Next, create a new widget in protected/components called DisqusWidget.php. This widget will be loaded within our view and will be populated by our Content model: class DisqusWidget extends CWidget {} Begin by specifying the public properties that our view will be able to inject into as follows: public $shortname = NULL; public $identifier = NULL; public $url = NULL; public $title = NULL; Then, overload the init() method to load the Disqus JavaScript callback and to populate the JavaScript variables with those populated to the widget as follows:public function init() public function init(){parent::init();if ($this->shortname == NULL)throw new CHttpException(500, 'Disqus shortname isrequired');echo "<div id='disqus_thread'></div>";Yii::app()->clientScript->registerScript('disqus', "var disqus_shortname = '{$this->shortname}';var disqus_identifier = '{$this->identifier}';var disqus_url = '{$this->url}';var disqus_title = '{$this->title}';/* * * DON'T EDIT BELOW THIS LINE * * */(function() {var dsq = document.createElement('script'); dsq.type ='text/javascript'; dsq.async = true;dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);})();");} Finally, within our themes/main/views/content/view.php file, load the widget as follows: <?php $this->widget('DisqusWidget', array('shortname' => Yii::app()->params['includes']['disqus']['shortname'],'url' => $this->createAbsoluteUrl('/'.$post->slug),'title' => $post->title,'identifier' => $post->id)); ?> Now, when you load any given post, Disqus comments will also be loaded with that post. Go ahead and give it a try! Searching for content Next, we'll implement a search method so that our users can search for posts. To do this, we'll implement an instance of CActiveDataProvider and pass that data to our themes/main/views/content/all.php view to be rendered and paginated: public function actionSearch(){$param = Yii::app()->request->getParam('q');$criteria = new CDbCriteria;$criteria->addSearchCondition('title',$param,'OR');$criteria->addSearchCondition('body',$param,'OR');$dataprovider = new CActiveDataProvider('Content', array('criteria'=>$criteria,'pagination' => array('pageSize' => 5,'pageVar'=>'page')));$this->render('//content/all', array('dataprovider' => $dataprovider));} Since our view file already exists, we can now search for content in our CMS. Managing content Next, we'll implement a basic set of management tools that will allow us to create, update, and delete entries: We'll start by defining our loadModel() method and the actionDelete() method: private function loadModel($id=NULL){if ($id == NULL)throw new CHttpException(404, 'No category with that IDexists');$model = Content::model()->findByPk($id);if ($model == NULL)throw new CHttpException(404, 'No category with that IDexists');return $model;}public function actionDelete($id){$this->loadModel($id)->delete();$this->redirect($this->createUrl('content/admin'));} Next, we can implement our admin view, which will allow us to view all the content in our system and to create new entries. Be sure to copy the themes/main/views/content/admin.php file from the project resources folder into your project before using this view: public function actionAdmin(){$model = new Content('search');$model->unsetAttributes();if (isset($_GET['Content']))$model->attributes = $_GET;$this->render('admin', array('model' => $model));} Finally, we'll implement a save view to create and update entries. Saving content will simply pass it through our content model's validation rules. The only override we'll be adding is ensuring that the author is assigned to the user editing the entry. Before using this view, be sure to copy the themes/main/views/content/save.php file from the project resources folder into your project: public function actionSave($id=NULL){if ($id == NULL)$model = new Content;else$model = $this->loadModel($id);if (isset($_POST['Content'])){$model->attributes = $_POST['Content'];$model->author_id = Yii::app()->user->id;if ($model->save()){Yii::app()->user->setFlash('info', 'The articles wassaved');$this->redirect($this->createUrl('content/admin'));}}$this->render('save', array('model' => $model));} At this point, you can now log in to the system using the credentials provided in the following table and start managing entries: Username Password user1@example.com test user2@example.com test Summary In this article, we dug deeper into Yii framework by manipulating our CUrlManager class to generate completely dynamic and clean URIs. We also covered the use of Yii's built-in theming to dynamically change the frontend appearance of our site by simply changing a configuration value. Resources for Article: Further resources on this subject: Creating an Extension in Yii 2 [Article] Yii 1.1: Using Zii Components [Article] Agile with Yii 1.1 and PHP5: The TrackStar Application [Article]
Read more
  • 0
  • 0
  • 2435
article-image-docker-container-management-with-saltstack
Nicole Thomas
25 Sep 2014
8 min read
Save for later

Docker Container Management at Scale with SaltStack

Nicole Thomas
25 Sep 2014
8 min read
Every once in a while a technology comes along that changes the way work is done in a data center. It happened with virtualization and we are seeing it with various cloud computing technologies and concepts. But most recently, the rise in popularity of container technology has given us reason to rethink interdependencies within software stacks and how we run applications in our infrastructures. Despite all of the enthusiasm around the potential of containers, they are still just another thing in the data center that needs to be centrally controlled and managed...often at massive scale. This article will provide an introduction to how SaltStack can be used to manage all of this, including container technology, at web scale. SaltStack Systems Management Software The SaltStack systems management software is built for remote execution, orchestration, and configuration management and is known for being fast, scalable, and flexible. Salt is easy to set up and can easily communicate asynchronously with tens of thousands of servers in a matter of seconds. Salt was originally built as a remote execution engine relying on a secure, bi-directional communication system utilizing a Salt Master daemon used to control Salt Minion daemons, where the minions receive commands from the remote master. Salt’s configuration management capabilities are called the state system, or Salt States, and are built on SLS formulas. SLS files are data structures based on dictionaries, lists, strings, and numbers that are used to enforce the state that a system should be in, also known as configuration management. SLS files are easy to write, simple to implement, and are typically written in YAML. State file execution occurs on the Salt Minion. Therefore, once any states files that the infrastructure requires have been written, it is possible to enforce state on tens of thousands of hosts simultaneously. Additionally, each minion returns its status to the Salt Master. Docker containers Docker is an agile runtime and packaging platform specializing in enabling applications to be quickly built, shipped, and run in any environment. Docker containers allow developers to easily compartmentalize their applications so that all of the program’s needs are installed and configured in a single container. This container can then be dropped into clouds, data centers, laptops, virtual machines (VMs), or infrastructures where the app can execute, unaltered, without any cross-environment issues. Building Docker containers is fast, simple, and inexpensive. Developers can create a container, change it, break it, fix it, throw it away, or save it much like a VM. However, Docker containers are much more nimble, as the containers only possess the application and its dependencies. VMs, along with the application and its dependencies, also install a guest operating system, which requires much more overhead than may be necessary. While being able to slice deployments into confined components is a good idea for application portability and resource allocation, it also means there are many more pieces that need to be managed. As the number of containers scales, without proper management, inefficiencies abound. This scenario, known as container sprawl, is where SaltStack can help and the combination of SaltStack and Docker quickly proves its value. SaltStack + Docker When we combine SaltStack’s powerful configuration management armory with Docker’s portable and compact containerization tools, we get the best of both worlds. SaltStack has added support for users to manage Docker containers on any infrastructure. The following demonstration illustrates how SaltStack can be used to manage Docker containers using Salt States. We are going to use a Salt Master to control a minimum of two Salt Minions. On one minion we will install HAProxy. On the other minion, we will install Docker and then spin up two containers on that minion. Each container hosts a PHP app with Apache that displays information about each container’s IP address and exposed port. The HAProxy minion will automatically update to pull the IP address and port number from each of the Docker containers housed on the Docker minion. You can certainly set up more minions for this experiment, where additional minions would be additional Docker container minions, to see an implementation of containers at scale, but it is not necessary to see what is possible. First, we have a few installation steps to take care of. SaltStack’s installation documentation provides a thorough overview of how to get Salt up and running, depending on your operating system. Follow the instructions on Salt’s Installation Guide to install and configure your Salt Master and two Salt Minions. Go through the process of accepting the minion keys on the Salt Master and, to make sure the minions are connected, run: salt ‘*’ test.ping Note: Much of the Docker functionality used in this demonstration is brand new. You must install the latest supported version of Salt, which is 2014.1.6. For reference, I have my master and minions all running on Ubuntu 14.04. My Docker minion is named “docker1” and my HAProxy minion is named “haproxy”. Now that you have a Salt Master and two Salt Minions configured and communicating with each other, it’s time to get to work. Dave Boucha, a Senior Engineer at SaltStack, has already set up the necessary configuration files for both our haproxy and docker1 minions and we will be using those to get started. First, clone the Docker files in Dave’s GitHub repository, dock-apache, and copy the dock_apache and docker> directories into the /srv/salt directory on your Salt Master (you may need to create the /srv/salt directory): cp -r path/to/download/dock_apache /srv/salt cp -r path/to/download/docker /srv/salt The init.sls file inside the docker directory is a Salt State that installs Docker and all of its dependencies on your minion. The docker.pgp file is a public key that is used during the Docker installation. To fire off all of these events, run the following command: salt ‘docker1’ state.sls docker Once Docker has successfully installed on your minion, we need to set up our two Docker containers. The init.sls file in the dock_apache directory is a Salt State that specifies the image to pull from Docker’s public container repository, installs the two containers, and assigns the ports that each container should expose: 8000 for the first container and 8080 for the second container. Now, let’s run the state: salt ‘docker1’ state.sls dock_apache Let’s check and see if it worked. Get the IP address of the minion by running: salt ‘docker1’ network.ip_addrs Copy and paste the IP address into a browser and add one of the ports to the end. Try them both (IP:8000 and IP:8080) to see each container up and running! At this point, you should have two Docker containers installed on your “docker1” minion (or any other docker minions you may have created). Now we need to set up the “haproxy” minion. Download the files from Dave’s GitHub repository, haproxy-docker, and place them all in your Salt Master’s /srv/salt/haproxy directory: cp -r path/to/download/haproxy /srv/salt First, we need to retrieve the data about our Docker containers from the Docker minion(s), such as the container IP address and port numbers, by running the haproxy.config salt ‘haproxy’ state.sls haproxy.config By running haproxy.config, we are setting up a new configuration with the Docker minion data. The configuration file also runs the haproxy state (init.sls. The init.sls file contains a haproxy state that ensures that HAProxy is installed on the minion, sets up the configuration for HAProxy, and then runs HAProxy. You should now be able to look at the HAProxy statistics by entering the haproxy minion’s IP address in your browser with /haproxy?stats on the end of the URL. When the authentication pop-up appears, the username and password are both saltstack. While this is a simple example of running HAProxy to balance the load of only two Docker containers, it demonstrates how Salt can be used to manage tens of thousands of containers as your infrastructure scales. To see this same demonstration with a haproxy minion in action with 99 Docker minions, each with two Docker containers installed, check out SaltStack’s Salt Air 20 tutorial by Dave Boucha on YouTube. About the author Nicole Thomas is a QA Engineer at SaltStack, Inc. Before coming to SaltStack, she wore many hats from web and Android developer to contributing editor to working in Environmental Education. Nicole recently graduated Summa Cum Laude from Westminster College with a degree in Computer Science. Nicole also has a degree in Environmental Studies from the University of Utah.
Read more
  • 0
  • 0
  • 12855

article-image-introduction-oracle-bpm
Packt
25 Sep 2014
23 min read
Save for later

Introduction to Oracle BPM

Packt
25 Sep 2014
23 min read
In this article, Vivek Acharya, the author of the book, Oracle BPM Suite 12c Modeling Patterns, has discussed the various patterns in Oracle. The spectrum of this book covers patterns and scenarios from strategic alignment (the goals and strategy model) to flow patterns. From conversation, collaboration, and correlation patterns to exception handling and management patterns; from human task patterns and business-IT collaboration to adaptive case management; and many more advance patterns and features have been covered in this book. This is an easy-to-follow yet comprehensive guide to demystify the strategies and best practices for developing BPM solutions on the Oracle BPM 12c platform. All patterns are complemented with code examples to help you better discover how patterns work. Real-life scenarios and examples touch upon many facets of BPM, where solutions are a comprehensive guide to various BPM modeling and implementation challenges. (For more resources related to this topic, see here.) In this section, we will cover dynamic task assignment patterns in detail, while a glimpse of the strategic alignment pattern is offered. Dynamic task assignment pattern Business processes need human interactions for approvals, exception management, and interaction with a running process, group collaboration, document reviews or case management, and so on. There are various requirements for enabling human interaction with a running BPMN process, which are accomplished using human tasks in Oracle BPM. Human tasks are implemented by human workflow services that are responsible for the routing of tasks, assignment of tasks to users, and so on. When a token arrives at the user task, control is passed from the BPMN process to Oracle Human Workflow, and the token remains with human tasks until it's completed. As callbacks are defined implicitly, once the workflow is complete, control is returned back to the user task and the token moves ahead to subsequent flow activities. When analyzing all the human task patterns, it's evident that it comprises various features, such as assignment patterns, routing patterns, participant list builder patterns, and so on. Oracle BPM offers these patterns as a template for developers, which can be extended. The participants are logically grouped using stages that define the assignment with a routing slip. The assignment can be grouped based on assignment modeling patterns. It could be sequential, parallel, or hybrid. Routing of the tasks to participants is governed by the routing pattern, which is a behavioral pattern. It defines whether one participant needs to act on a task, many participants need to act in sequence, all the participants need to act in parallel, or participants need not act at all. The participants of the task are built using participant list building patterns, such as approval groups, management chains, and so on. The assignment of the participant is performed by a task assignment mechanism, such as static, dynamic, or rule-based assignment. In this section, we will be talking about the task assignment pattern. The intent of the task assignment pattern is the assignment of human tasks to user(s), group(s), and/or role(s). Essentially, it's being motivated with the fact that you need to assign participants to tasks either statically, dynamically, or on derivations based on business rules. We can model and define task assignment at runtime and at design time. However, the dynamic task assignment pattern deals with the assignment of tasks at runtime. This means the derivation of task participants will be performed at runtime when the process is executing. This pattern is very valuable in business scenarios where task assignment cannot be defined at design time. There are various business requirements that need task assignments based on the input data of the process. As task assignment will be based on the input data of the process, task assignments will be evaluated at runtime; hence, these are termed as dynamic task assignments. The following pattern table gives the details of the dynamic task assignment pattern: Signature Dynamic Task Assignment Pattern Classification Human Task Pattern Intent To dynamically assign tasks to the user/group/roles. Motivation Evaluating task assignment at runtime. Applicability Dynamic task assignment is applicable where the distribution of work is required. It's required when you need multilevel task assignment or for the evaluation of users/roles/groups at runtime. Implementation Dynamic task assignment can be implemented using complex process models, business rules, organization units, organizational roles (parametric roles), external routing, and so on. Known issues What if the users that are derived based on the evaluation of dynamic conditions have their own specific preferences? Known solution Oracle BPM offers rules that can be defined in the BPM workspace. Defining the use case We will walk though an insurance claim process that has two tasks to be performed: the first task is verification, and the other is the validation of claimant information. The insurance claim needs to be verified by claim agents. Agents are assigned verification tasks based on the location and the business organization unit they belong to. An agent's skills in handling claim cases are also considered based on the case's sensitivity. If the case's sensitivity requires an expert agent, then an expert agent assigned to that organization unit and belonging to that specific location must be assigned. Think of this from the perspective of having the same task being assigned to different users/groups/roles based on different criteria. For example, if the input payload has Regular as the sensitivity and EastCoast as the organization unit, then the verification task needs to be assigned to an EastCoast-Regular agent. However, if the input payload has EastCoast-Expert as the sensitivity and EastCoast as the organization unit, then the verification task needs to be assigned to an EastCoast-Expert agent. Then, we might end up with multiple swimlanes to fulfill such a task assignment model. However, such models are cumbersome and complicated, and the same activities will be duplicated, as you can see in the following screenshot: The routing of tasks can be modeled in human tasks as well as in the BPMN process. It depends purely on the business requirements and on various modeling considerations. For instance, if you are looking for greater business visibility and if there is a requirement to use exceptional handling or something similar, then it's good to model tasks in the BPMN process itself. However, if you are looking for dynamism, abstraction, dynamic assignment, dynamic routing, rule-driven routing, and so on, then modeling task routing in human task assignment and routing is an enhanced modeling mechanism. We need an easy-to-model and dynamic technique for task assignment. There are various ways to achieve dynamic task assignment: Business rules Organization units Organizational roles (parametric roles) We can use business rules to define the condition(s), and then we can invoke various seeded List Builder functions to build the list of participants. This is one way of enabling dynamic task assignment using business rules. Within an organization, departments and divisions are represented by organizational units. You can define the hierarchy of the organization units, which corresponds to your organizational structure. User(s), group(s), role(s), and organizational roles can be assigned as members to organization units. When a task belonging to a process is associated with an organization unit, then the task is available for the members of that organization unit. So, using the scenario we discussed previously, we can define organization units and assign members to them. We can then associate the process with the organization unit, which will result in task assignment to only those members who belong to the organization that is associated with the process. Users have various properties (attributes) defined in LDAP or in the Oracle Internet Directory (OID); however, there are cases where we need to define additional properties for the users. Most common among them is the definition of roles and organization units as properties of the user. There are cases where we don't have the flexibility to extend an enterprise LDAP/OID for adding these properties. What could be the solution in this case? Extended user properties are the solution in such scenarios. Using an admin user, we can define extended properties for the users in the BPM workspace. Once the extended properties are defined, they can be associated with the users. Let's extend the scenario we were talking about previously. Perform the following tasks: Define the agent group in the myrealm weblogic, which contains users as its members. Define two organization units, one for EastCoast and another for West Coast. The users of the agent group will be assigned as members to organization units. Define extended properties. Associate extended properties with the users (these users are members of the agent group). Define organizational roles, and create a process with a user task that builds the participant list using the organizational role defined previously. The features of the use case are as follows: A validation task gets assigned to the insurance claim agent group (the ClaimAgents group) The assignment of the task to be used is dynamically evaluated based on the value of the organization unit being passed as input to the process Once the validation task is acted upon by the participants (users), the process token reaches the verification task The assignment of the verification task is based on the organization role (the parametric role) being defined Assigning users to groups In the course of achieving the use case scenario, we will execute the following tasks to define a group, organization units, extended properties, and parametric roles: Log in to the Oracle weblogic console and navigate to myrealm. Click on the Users & Groups tab and select the Groups tab. Click on the new group with the name ClaimAgents and save the changes. Click on Users; create the users (anju, rivi, and buny) and assign them to the ClaimAgents group as members. Click on the following users and assign them to the ClaimAgents group: jausten, jverne, mmitch, fkafka, achrist, cdickens, wshake, and rsteven. Click on Save. We have defined and assigned some new users and some of the already existing users to the ClaimAgents group. Now, we will define the organization units and assign users as members to the organization units. Perform the following steps: Log in to the Oracle BPM workspace to define the organization units. Go to Administration | Organization | Organization Units. Navigate to Create Organization Unit | Root Organization Unit in order to create a parent organization unit. Name it Finance. Go to Create Organization Unit | Child Organization Unit in order to create child organization units. Name it EastCoastFinOrg and WestCoastFinOrg: Assign the following users as members to the to the EastCoastFinOrg organization unit: Mmitch, fkafka, jverne, jausten, achrist, and anju Assign the following users as members to the WestCoastFinOrg organization unit: rivi, buny, cdickens, wshake, rsteven, and jstein Defining extended properties The users are now assigned as members to organization units. Now, it's time to define the extended properties. Perform the following steps: In the BPM workspace, navigate to Administration | Organization | Extended User Properties. Click on Add Property to define the extended properties, which are as follows: Property Name Value Sensitivity Expert, regular Location FL, CA, and TX Add Users should be clicked on to associate the properties of users in the Map Properties section on the same page, as shown previously. Use the following mapping to create the map of extended properties and users: User Name Sensitivity Location mmitch Regular FL fkafka Regular FL jverne Regular FL jausten Expert FL achrist Expert FL anju Expert FL rivi Expert CA buny Expert CA cdickens Regular CA wshake Regular CA rsteven Regular CA jstein Expert CA Defining the organization role We will define the organizational role (the parametric role) and use it for task assignment in the case/BPM process. The organizational role (parametric role) as task assignment pattern is used for dynamic task assignment because users/roles are assigned to the parametric role based on the evaluation of the condition at runtime. These conditions are evaluated at runtime for the determination of users/roles based on organization units and extended properties. Perform the following steps: Go to Administration | Organization | Parametric Roles. Click on Create Parametric Role to create a role and name it AgentRole. Click on Add Parameter to define the parameters for the parametric role and name them as Sensitivity of string type and Location of string type. You can find the Sensitivity and Location extended properties in the Add Condition drop-box. Select Grantees as Group; browse and select the ClaimAgents group from the LDAP. Select Sensitivity and click on the + sign to include the property in the condition. For the sensitivity, use the Equals operator and select the parametric role's input parameter from the drop-down list. This parameter will be listed as $Sensitivity. Similarly, select $Location and click on the + sign to include the property in the condition. For the location, use the Equals operator and select the parametric role's input parameter from the drop-down list. This parameter will be listed as $Location. This is shown in the following screenshot: Click on Save to apply the changes. As we can see in the preceding screenshot, the organization unit is also available as a property that can be included in the condition. We have configured the parametric role with a specific condition. The admin can log in and change the conditions as and when the business requires (changes at runtime, which bring in agility and dynamism). Implementing the BPMN process We can create a BPM process which has a user task that builds a participant's list using the parametric role, as follows: Create a new BPM project with the project name as DynamicTaskAssignment. Create the composite with a BPMN component. Create an asynchronous BPMN process and name it as VerificationProcess. Create a business object, VerificationProcessBO, based on the InsuranceClaim.xsd schema. The schema (XSD) can be found in the DynamicTaskAssignment project in this article. You can navigate to the schemas folder in the project to get the XSD. Define the process input argument as VerificationProcessIN, which should be based on the VerificationProcessBO business object. Similarly, define the process output argument as VerificationProcessOUT, based on VerificationProcessBO. Define two process data objects as VerificationProcessINPDO and VerificationProcessOUTPDO, which are based on the VerificationProcessBO business object. Click on the message start event's data association and perform the data association as shown in the following screenshot. As we can see, the process input is assigned to a PDO; however, the organization unit element from the process input is assigned to the predefined variable, organizationUnit. This is shown in the following screenshot: The validation task is implemented to demonstrate dynamic task assignment using multilevel organization units. We will check the working of this task when we perform the test. Perform the following steps: Drag-and-drop a user task between the message start event and the message end event and name it ValidationTask. Go to the Implementation tab in the user task property dialog and click on Add to create a human task; name it ValidationTask. Enter the title of the task with outcomes as Accept and Reject. Let the input parameter for ValidationTask be VerificationProcessINPDO. Click on OK to finish the task configuration. This will bring you back to the ValidationTask property dialog box. Perform the necessary data association. Click on ValidationTask.task to open the task metadata, and go to the Assignment section in the task metadata. Click on the Participant block. This will open the participant type dialog box. Select the Parallel Routing Pattern. Select the list building pattern as Lane Participant ( the current lane). For all other values, use the default settings. Click on Save All. Now, we will create a second task in the same process, which will be used to demonstrate dynamic task assignment using the organization role (the parametric role). Perform the following steps: Drag-and-drop a user task between Validation Task and the message end event in the verification process and name it as Verification Task. Go to the Implementation tab in the user task property dialog and click on Add to create a human task. Enter the title of the task as Verification Task, with the Accept and Reject outcomes. Let the input parameter for the Verification Task be VerificationProcessINPDO. Click on OK to finish the task configuration. This will bring you back to VerificationTask property dialog. Click on VerificationTask.task to open the task metadata and go to the Assignment section in the task metadata. Click on the Participant block. This will open the Participant Type dialog box. Select parallel routing pattern. Select list building pattern as Organization Role. This is shown in the following screenshot: Enter the name of the organizational role as AgentRole, which is the organizational role we have defined previously: Along with the organizational role, enter the input arguments for the organizational role. For the Sensitivity input argument of the parametric role, use the XPath expression to browse the input payload and select Sensitivity Element, as shown previously. Similarly, for the Location argument, select State Element from the input payload. Click on Save All. This article contains downloads for DynamicTaskAssignment, which we have already created to facilitate verification. If you have not created the project by following the steps mentioned previously, you can use the project delivered in the download. Deploy the project to the weblogic server. Log in to the BPM workspace as the admin user and go to Administrator | Organization units. Click on roles to assign the ClaimAgents group to the DynamicTaskAssignmentPrj.ClaimAgents role. Click on Save. Testing the dynamic task assignment pattern Log in to the EM console as an admin user to test the project; however, you can use any tool of your choice to test it. We can get the test data from the project itself. Navigate to DynamicTaskAssignment | SOA | Testsuites | TestData12c.xml to find the test data file. Use the TestData.xml file if you are going to execute the project in the 11g environment. The test data contains values where the sensitivity is set to Expert, organization unit is set to Finance/EastCoastFinOrg, and state is set to CA. The following are the test results of the validation task: Org Unit Input Value Validation Task Assignment Finance/EastCoastFinOrg mmitch, fkafka, jverne, jausten, achrist, anju, and jstein Finance jausten, jverne, mmitch, fkafka, achrist, cdickens, wshake, rsteven, rivi, buny, jstein, and anju Note that if you pass the organization unit as Finance, then all the users belonging to the finance organization's child organization will receive the task. However, if you pass the organization unit as Finance/EastCoastFinOrg (EastCoastFinOrg is a child organization in the finance parent organization), then only those users who are in the EastCoastFinOrg child organization will receive the task. The process flow will move from the validation task to the verification task only when 50 percent of the participants act on the validation task, as parallel routing pattern is defined with the voting pattern of 50 percent. The following are the test results for Verification Task: Input Verification Task Sensitivity: Expert buny, rivi, and jstein Location: CA N/A Based on the extended properties mapping, the verification task will get assigned to the users buny, rivi, and jstein. Addressing known issues What if the users are derived based on the evaluation of dynamic conditions? To address this, Oracle BPM offers rules that can be defined in the BPM workspace. We will extend the use case we have defined. When we execute the verification process, the verification task is assigned to the buny, rivi, and jstein users. How will you address a situation where the user, buny, wants the task to be reassigned or delegated to someone when he is in training and cannot act on the assigned tasks? Perform the following steps: Log in to the Oracle BPM workspace as the user (buny) for whom we want to set the preference. Go to Preferences | Rules, as shown in the following screenshot: Click on + to create a new rule. Enter the name of the rule as TrainingRule. Specify the rule condition for the use case user (buny). We have defined the rule condition that executes the rule when the specified dates are met and the rule is applicable to all the tasks: Specify the dates (Start Date and End Date) between which we want the rule to be applicable. (These are the dates on which the user, buny, will be in training). Define the rule condition either by selecting all the tasks or by specifying matching criteria for the task. Hence, when the task condition is evaluated to True, the rule gets executed. Reassign the task to the jcooper user when the rule gets evaluated to True. The rule action constitutes of task reassignment or delegation, or we can specify a rule that takes no action. The rule action is to change task assignment or to allow someone else to perform on behalf of the original assignee, as described in the following points: Select reassignment if you want the task to be assigned to another assignee, who can work on the task as if the task was assigned to him/her Select delegation if you want the assignee to whom the task is delegated to work on behalf of the original assignee Execute the DynamicTaskAssignment project to run the verification process. When the verification task gets executed, log in to the EM console and check the process trace. As we can see in the following screenshot, the task gets reassigned to the jcooper user. We can log in to the BPM workspace as the jcooper user and can also verify the task in the task list. This is shown in the following screenshot: There's more The dynamic task assigned pattern brings in dynamism and agility to the business process. In the preceding use case, we have passed organization unit as the process input parameter. However, with Oracle BPM 12c, we can define business parameters and use them to achieve greater flexibility. The business parameters allow business owners to change the business parameter's value at runtime without changing the process, which essentially allows you to change the process without the inclusion of IT. Basically, business parameters are already used in the process and they are driving the process flow. Changing the value of business parameters at runtime is like changing the process flow at execution. For the preceding use case, the insurance input schema (parameter) has an organization unit that is passed when invoking the process. However, what if there are no placeholders to pass the organization unit in the input parameter? We can define a business parameter in JDeveloper and assign the value to the organization unit. This is shown in the following screenshot: Perform the following steps to deploy the project. Open JDeveloper 12c and expand the project to navigate to Organization | Business Parameters. Define a business parameter with the name ORGUNIT and of the type String; enter a default value and save the changes. Go to the process message start event and navigate to the Implementation tab. Click on data association and assign business parameters to the predefined variable (organization unit). Save and deploy the project. Using the shown mechanism, a developer can enable business parameters; technically, the BPMN engine executes the following function to get the business parameter value: bpmn:getBusinessParameter('Business Parameter') Similarly, a process analyst can click on the BPM composer application and bring about changes in the process to define business parameters and changes in the process. Process asset manager (PAM) will take care of asset sharing and collaboration. Business owners can log in to the BPM workspace application and change the business parameters by navigating to the following path to edit the parameter values to drive/modify the process flow: Administration | Organization | Business Parameter Strategic alignment pattern BPMN needs a solution to align business goals, objectives, and strategies. It also needs a solution to allow business analysts and function/knowledge workers to create business architecture models that drive the IT development of a process, which remains aligned with the goals and objectives. Oracle BPM 12c offers business architecture, a methodology to perform high-level analysis of business processes. This methodology adopts a top-down approach for discovering organizational processes, defining goals and objectives, defining strategies and mapping them to goals and objectives, and reporting the BA components. All these details are elaborated exclusively with use cases and demo projects in the book. The following pattern table highlights facts around the strategic alignment pattern: Signature Strategic Alignment Pattern Classification Analysis and Discovery Pattern Intent To offer a broader business model (an organizational blueprint) that ensures the alignment of goals, objectives, and strategies with organizational initiatives. Motivation A BPMN solution should offer business analysts and functional users with a set of features to analyze, refine, define, optimize, and report business processes in the enterprise. Applicability Such a solution will only empower businesses to define models based on what they actually need, and reporting will help to evaluate the performances. This will then drive the IT development of the processes by translating requirements into BPMN processes and cases. Implementation Using BPM composer, we can define goals, objectives, strategies, and value chain models. We can refer to BPMN processes from the value chain models. Goals are broken down into objects, which are fulfilled by strategies. Strategies are implemented by value chains, which can be decomposed into value chains/business processes. Known issues The sharing of assets between IT developers, business architects, and process analysts. Known solution Oracle BPM 12c offers PAM, which is a comprehensive solution, and offers seamless asset sharing and collaboration between business and IT. This book covers PAM exclusively. Summary In this article, we have just introduced the alignment pattern. However, in the book, alignment pattern is covered in detail. It shows how IT development and process models can be aligned with organization goals. While performing alignments, we will learn enterprise maps, strategy models, and value chain models. We will discover how models are created and linked to an organization. Capturing the business context showcases the importance of documentation in the process model phase. Different document levels and their methods of definition are discussed along with their usage. Further, we learned how to create different reports based on the information we have documented in the process, such as RACI reports and so on. The process player demonstration showcased how process behavior can be emulated in a visual representation, which allows designers and analysts to test and revise a process without deploying it. This infuses a preventive approach and also enables organizations to quickly find the loopholes, making them more responsive to challenges. We also elaborated on how round trips and business-IT collaboration facilitates the storing, sharing, and collaboration of process assets and business architecture assets. While doing so, we witnessed PAM and subversion as well as learnt versioning, save/update/commit, difference and merge, and various other activities that empower developers and analysts to work in concert. In this book, we will learn various patterns in a similar format. Each pattern pairs the classic problem/solution format, which includes signature, intent, motivation, applicability, and implementation; the implementation is demonstrated via a use case scenario along with a BPMN application, in each chapter. It's a one-stop title to learn about patterns, their applicability and implementation, as well as BPMN features. Resources for Article: Further resources on this subject: Oracle GoldenGate- Advanced Administration Tasks - I [article] Oracle B2B Overview [article] Oracle ADF Essentials – Adding Business Logic [article]
Read more
  • 0
  • 0
  • 2314
Modal Close icon
Modal Close icon