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
Events
Videos
Audiobooks
Packt Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials

7018 Articles
article-image-understanding-text-search-and-hierarchies-sap-hana
Packt
20 Oct 2015
9 min read
Save for later

Understanding Text Search and Hierarchies in SAP HANA

Packt
20 Oct 2015
9 min read
In this article by Vinay Singh, author of the book Real Time Analytics with SAP HANA, this article covers Full Text Search and hierarchies in SAP HANA, and how to create and use them in our data models. After completing this article, you should be able to: Create and use Full Text Search Create hierarchies—level and parent child hierarchies (For more resources related to this topic, see here.) Creating and using Full Text Search Before we proceed with the creation and use of Full Text Search, let's quickly go through the basic terms associated with it. They are as follows: Text Analysis: This is the process of analyzing unstructured text, extracting relevant information, and then transforming this information into structure information that can be leveraged in different ways. The scripts provide additional possibilities to analyze strings or large text columns by providing analysis rules for many industries in many languages in SAP HANA. Full Text Search: This capability of HANA helps to speed up search capabilities within large amounts of text data significantly. The primary function of Full Text Search is to optimize linguistic searches. Fuzzy Search: This functionality enables to find strings that match a pattern approximately (rather than exactly). It's a fault-tolerant search, meaning that a query returns records even if the search term contains additional or missing characters, or even spelling mistakes. It is an alternative to a non-fault tolerant SQL statement. The score() function: When using contains() in the where clause of a select statement, the score() function can be used to retrieve the score. This is a numeric value between 0.0 and 1.0. The score defines the similarity between the user input and the records returned by the search. A score of 0.0 means that there is no similarity. The higher the score, the more similar a record is to the search input. Some of the applied applications of fuzzy search could be: Fault-tolerant check for duplicate records. Its helps to prevent duplication entry in Systems by searching similar entries. Fault-tolerant search in text columns—for example, search documents on diode and find all documents that contain the term "triode". Fault-tolerant search in structure database content search for rhyming words, for example coffee Krispy biscuit and find toffee crisp biscuits (the standard example given by SAP). Let's see what are the use cases for text search: Combining structure and unstructured data Medicine and healthcare Patents Brand monitoring and the buying pattern of consumer Real-time analytics on a large volume of data Data from social media Finance data Sales optimization Monitoring and production planning The results of text analysis are stored in a table and therefore, can be leveraged in all the HANA- supported scenarios: Standard Analytics: Create analytical views and calculation views on top. For example, companies mentioned in news articles over time. Data mining, predictive: Using R, Predictive Analysis Library (PAL) functions. For example, clustering, time series analysis, and so on. Search-based applications: Create a search model and build a search UI with the HANA Info Access (InA) toolkit for HTML5. Text analysis results can be used to navigate and filter search results. For example, People finder, search UI for internal documents. The capabilities of HANA Full Text Search and text analysis are as follows: Native full text search Database text analysis The graphical modeling of search models Info Access toolkit for HTML5 UIs. The benefits of full text search: Extract unstructured content with no additional cost Combine structure and unstructured information for unified information access Less data duplication and transfer Harness the benefit of InA (Info Access toolkit ) for an HTML5 application The following are the supported data types by fuzzy search: Short text Text VARCHAR NVARCHAR Date Data with full text index. Enabling search option Before we can use the search option in any attribute or analytical view, we will need to enable this functionality in the SAP HANA Studio Preferences as shown in the following screenshot: We are well prepared to move ahead with the creation and use of Full Text search. Let's do this step by step as follows: Create the table that we will use to perform the Full Text Search statements: Create Schema <DEMO>; // I am creating , it would be already present from our previous exercises. SET SCHEMA DEMO; // Set the schema name Create a Column Table including FUZZY SEARCH indexed columns. DROP TABLE DEMO.searchtbl_FUZZY; CREATE COLUMN TABLE DEMO.searchtbl_FUZZY ( CUST_NAME TEXT FUZZY SEARCH INDEX ON, CUST_COUNTY TEXT FUZZY SEARCH INDEX ON, CUST_DEPT TEXT FUZZY SEARCH INDEX ON, ); Prepare the fuzzy search logic (SQL logic): Search for customers in the countries that contain the 'MAIN' word: SELECT score() AS score, * FROM searchtbl_FUZZY WHERE CONTAINS(cust_county, 'MAIN'); Search for customers in the countries that contain the 'MAIN' word but with Fuzzy parameter 0.4 SELECT score() AS score, * FROM searchtbl_FUZZY WHERE CONTAINS(cust_county, 'West', FUZZY(0.3)); Perform a fuzzy search for a customer working in a department that includes the department word : SELECT highlighted(cust_dept), score() AS score, * FROM searchtbl_FUZZY WHERE CONTAINS(cust_dept, 'Department', FUZZY(0.5)); Fuzzy search for all the columns by looking for the customer word: SELECT score() AS score, * FROM searchtbl_FUZZY WHERE CONTAINS(*, 'Customer', FUZZY(0.5)); Creating hierarchies Hierarchies are created to maintain data in a structured format, such as maintaining customer or employee data based on their roles and splitting the data based on geographies. Hierarchical data is very useful for organizational purposes during decision making. Two types of hierarchies can be created in SAP HANA: The level hierarchy Parent-child hierarchy The hierarchies are initially created in the attribute view and later can be combined in the analytic view or calculation view for consumption in a report as per business requirements. Let's create both types of hierarchies in attribute views. Creating level hierarchy Each level represents a position in the hierarchy. For example, a time dimension might have a hierarchy that represents data at the month, quarter, and year levels. Each level above the base level contains aggregate values for the levels below it. Create a new attribute view (for your own practice, I would suggest you to create a new one). You can also use an existing one. Use the SNWD_PD EPM sample tables. In output view, mark the following as output: In the semantic node of the view, create new hierarchy as shown in the following screenshot and fill the details: Save and Activate the view. Now the hierarchy is ready to be used in an analytical view. Add a client and node key again as output to your attribute view that you just created, that is AT_LEVEL_HIERARCY_DEMO, as we will use these two fields in Create an analytical view. It should look like the following screenshot. Add the attribute view created in the preceding step and the SNWD_SO_I table to the data foundation: Join client to client and product guide to node key:  Save and activate. Go to MS Excel | All Programs | Microsoft Office | Microsoft Excel 2010 then go to Data tab | From Other Sources | From Data Connection Wizard. You will get a new popup for Data Connection Wizard | Other/Advanced | SAP HANA MDX Provider: You will be asked to provide the connection details, fill the details, and test the connection (these are the same details that you used while adding the system to SAP HANA Studio). Data Connection Wizard will now ask you to choose the analytical view (choose the one that you just created in the preceding step): The preceding steps will take you to an excel sheet and you will see data as per the choices that you chose in the Pivot table field list: Create parent-child hierarchy The parent-child hierarchy is a simple, two-level hierarchy where the child element has an attribute containing the parent element. These two columns define the hierarchical relationships among the members of the dimension. The first column, called the member key column, identifies each dimension member. The other column, called the parent column, identifies the parent of each dimension member. The parent attribute determines the name of each level in the parent-child hierarchy and determines whether the data for parent members should be displayed  Let's create a parent-child hierarchy using the following steps: Create an attribute view. Create a table that has the parent-child information: The following is the sample code and the insert statement: CREATE COLUMN TABLE "DEMO"."CCTR_HIE"( "CC_CHILD" NVARCHAR(4), "CC_PARENT" NVARCHAR(4)); insert into "DEMO"."CCTR_HIE" values('','') insert into "DEMO"."CCTR_HIE" values('C11','c1'); insert into "DEMO"."CCTR_HIE" values('C12','c1'); insert into "DEMO"."CCTR_HIE" values('C13','c1'); insert into "DEMO"."CCTR_HIE" values('C14','c2'); insert into "DEMO"."CCTR_HIE" values('C21','c2'); insert into "DEMO"."CCTR_HIE" values('C22','c2'); insert into "DEMO"."CCTR_HIE" values('C31','c3'); insert into "DEMO"."CCTR_HIE" values('C1','c'); insert into "DEMO"."CCTR_HIE" values('C2','c'); insert into "DEMO"."CCTR_HIE" values('C3','c'); We will put the preceding table into our data foundation of attribute view as follows: Make CC_CHILD as the key attribute. Now let's create new hierarchy as shown in the following screenshot: Save and activate the hierarchy. Create a new analytical view and add the HIE_PARENT_CHILD_DEMO view and the CCTR_COST table in data foundation. Join CCTR to CCTR_CILD with many is to one relationship. Make sure that in the semantic node, COST is set as a measure. Save and Activate the analytical view. Preview the data. As per the business need, we can use one of the two hierarchies along with attribute view or analytical view. Summary In this article, we took a deep dive into Full Text Search, fuzzy logic, and hierarchies concepts. We learned how to create and use text search and fuzzy logic. The parent-child and level hierarchies were discussed in detail with a hands-on approach on both. Resources for Article: Further resources on this subject: Sabermetrics with Apache Spark [article] Meeting SAP Lumira [article] Achieving High-Availability on AWS Cloud [article]
Read more
  • 0
  • 0
  • 16391

article-image-determining-resource-utilization-requirements
Packt
17 Feb 2014
11 min read
Save for later

Determining resource utilization requirements

Packt
17 Feb 2014
11 min read
(For more resources related to this topic, see here.) For those hoping to find a magical catch-all formula that will work in every scenario, you'll have to keep looking. Remember every environment is unique, and even where similarities may arise, the use case your organization has, will most likely be different from another organization. Beyond your specific VM resource requirements, the hosts you are installing ESXi on will also vary; the hardware available to you will affect your consolidation ratio (the number of virtual machines you can fit on a single host). For example, if you have 10 servers that you want to virtualize, and you have determined each requires 4 GB of RAM, you might easily virtualize those 10 servers on a host with 48 GB of memory. However, if your host only has 16 GB of memory, you may need two or three hosts in order to achieve the required performance. Another important aspect to consider is when to collect resource utilization statistics about your servers. Think about the requirements you have for a specific server; let's use your finance department as an example. You can certainly collect resource statistics over a period of time in the middle of the month, and that might work just fine; however, the people in your finance department are more likely to utilize the system heavily during the first few days of the month as they are working on their month end processes. If you collect resource statistics on the 15th, you might miss a huge increase in resource utilization requirements, which could lead to the system not working as expected, making unhappy users. One last thing before we jump into some example statistics; you should consider collecting these statistics over at least two periods for each server: First, during the normal business hours of your organization or the specific department, during a time when systems are likely to be heavily utilized The second round should include an entire day or week so you are aware of the impact of after hours tasks such as backups and anti-virus scans on your environment It's important to have a strong understanding of the use cases for all the systems you will be virtualizing. If you are running your test during the middle of the month, you might miss the increase of traffic for systems utilized heavily only at the end of the month, for example, accounting systems. The more information you collect, the better prepared you will be to determine your resource utilization requirements. There are quite a few commercial tools available to help determine the specific resource requirements for your environment. In fact, if you have an active project and/or budget, check with your server and storage vendor as they can most likely provide tools to assess your environment over a period of time to help you collect this information. If you work with a VMware Partner or the VMware Professional Services Organization (PSO), you could also work with them to run a tool called VMware Capacity Planner. This tool is only available to partners who have passed the corresponding partner exams. For purposes of this article, however, we will look at the statistics we can capture natively within an operating system, for example, using Performance Monitor on Windows and the sar command in Linux. If you are an OS X user, you might be wondering why we are not touching OS X. This is because while Apple allows virtualizing OS X 10.5 and later, it is only supported on the Apple hardware and is not likely an everyday use case. If your organization requires virtualizing OSX, ESXi 5.1 is supported on specific Mac Pro desktops with Intel Xeon 5600 series processors and 5.0 is supported on Xserve using Xeon 5500 series processors. The current Apple license agreement allows virtualizing OSX 10.5 and up; of course, you should check for the latest agreement to ensure you are adhering to the license agreement. Monitoring common resource statistics From a statistics perspective, there are four main types of resources you generally monitor: CPU, memory, disk, and network. Unless you have a very chatty application, network utilization is generally very low, but this doesn't mean we won't check on it; however, we probably won't dedicate as much time to it as we do for CPU, memory, and disk. As we think about the CPU and memory, we generally look at utilization in terms of percentages. When we look at example servers, you will see that having an accurate inventory of the physical server is important so we can properly gauge the virtual CPU and memory requirements when we virtualize. If a physical server has dual quad core CPUs and 16 GB of memory, it does not necessarily mean we want to provide the same amount of virtual resources. Disk performance is where many people spend the least amount of time, and those people generally have the most headaches after they have virtualized. Disk performance is probably the most critical aspect to think about when you are planning your virtualization project. Most people only think of storage in terms of storage capacity, generally gigabytes (GB) or terabytes (TB). However, from a server perspective, we are mostly concerned with the amount of input and output per second, otherwise known as IOPS and throughput. We break down IOPS in into reads and writes per second and then their ratio by comparing one with the other. Understanding your I/O patterns will help you design your storage architecture to properly support all your applications. Storage design and understanding is an art and science by itself. Sample workload Let's break this down into a practical example so we can see how we are applying these concepts. In this example, we will look at two different types of servers that are likely to have various resource requirements: Windows Active Directory Domain Controller and a CentOS Apache web server. In this scenario, let's assume that each of these server operating systems and applications are running on dedicated hardware, that is, they are not yet virtual machines. The first step you should take, if you do not have this already, is to document the physical systems, their components, and other relevant information such as computer or DNS name, IP address (es), location, and so on. For larger environments, you may also want to document installed software, user groups or departments, and so on. Collecting statistics on Windows On Windows servers, your first step would be to start performance monitoring. Perform the following steps to do so: Navigate to Start | Run and enter perfmon. Once the Performance Monitor window opens, expand Monitoring Tools and click on Performance Monitor. Here, you could start adding various counters; however, as of Windows 2008/Windows 7, Performance Monitor includes Data Collector Sets. Expand the Data Collector Sets folder and then the System folder; right-click on System Performance and select Start. Performance Monitor will start to collect key statistics about your system and its resource utilization. When you are satisfied that you have collected an appropriate amount of data, click on System Performance and select Stop. Your reports will be saved into the Reports folder; navigate to Reports| System, click on the System Performance folder, and finally double-click on the report to see the report. In the following screenshot for our domain controller, you can see we were using 10 percent of the total CPU resources available, 54 percent of the memory, a low 18 IOPS, and 0 percent of the available network resources (this is not really uncommon; I have had busy application servers that barely break 2 percent). Now let's compare what we are utilizing with the actual physical resources available to the server. This server has two dual core processors (four total cores) running at 2 GHz per core (8 GHz total available), 4 GB of memory, two 200 GB SAS drives configured in a RAID 1, and a 1 Gbps network card. Here, performance monitor shows averages, but you should also investigate peak usage. If you scroll down in the report, you will find a menu labeled CPU. Navigate to CPU | Process. Here you will see quite a bit of data, more than the space we have to review in this book; however, if you scroll down, you will see a section called Processor User Time by CPU. Here, your mean (that is, average) column should match fairly closely to the report overview provided for the total, but we also want to look at any spikes we may have encountered. As you can see, this CPU had one core that received a maximum of 35 percent utilization, slightly more than the average suggested. If we take the average CPU utilization at 10 percent of the total CPU, it means we will theoretically require only 800 MHz of CPU power, something a single physical core could easily support. The, memory is also using only half of what is available, so we can most likely reduce the amount of memory to 3 GB and still have room for various changes in operating conditions we might have not encountered during our collection window. Finally, having only 18 IOPS used means that we have plenty of performance left in the drives; even a SATA 7200 RPM drive can provide around 80 IOPS. Collecting statistics on Linux Now let's look at the Linux web server to see how we can collect this same set of information using sar in an additional package with sysstat that can monitor resource utilization over time. This is similar to what you might get from top or iotop. The sysstat package can easily be added to your system by running yum install sysstat, as it is a part of the base repository (yum install sysstat is command format). Once the sysstat package is installed, it will start collecting information about resource utilization every 10 minutes and keep this information for a period of seven days. To see the information, you just need to run the sar command; there are different options to display different sets of information , which we will look at next. Here, we can see that our system is idle right now by viewing the %idle column. A simple way to generate some load on your system is to run dd if=/dev/zero of=/dev/null, which will spike your CPU load to 100 percent, so, don't do this on production systems! Let's look at the output with some CPU load. In this example, you can see that the CPU was under load for about half of the 10-minute collection window. One problem here is that unless the CPU spike, in this case to 100 percent, was not consistent for at least 10 minutes, we would potentially miss these spikes using sar with a 10-minute window. This is easily changed by editing /etc/cron.d/sysstat, which tells the system to run this every 10 minutes. During a collection window, one or two minutes may provide more valuable detail. In this example, you can see I am now logging at a five-minute interval instead of 10, so I will have a better chance to find maximum CPU usage during my monitoring period. Now, we are not only concerned with the CPU, but we also want to see memory and disk utilization. To access those statistics, run sar with the following options: The sar –r command will show RAM (memory) statistics. At a basic level, the items we are concerned with here would be the percentage of memory used, which we could use to determine how much memory is actually being utilized. The sar –b command will show disk I/O. From a disk perspective, sar –b will tell us the total number of transactions per second (tps), read transactions per second (rtps), and write transactions per second (wtps). As you can see, you are able to natively collect quite a bit of relevant data about resource utilization on our systems. However, without the help of a vendor or VMware PSO who has access to VMware Capacity Planner, another commercial tool, or a good automation system, this can become difficult to do on a large scale (hundreds or thousands of servers), but certainly not impossible. Resources for Article: Further resources on this subject: Windows 8 with VMware View [Article] Troubleshooting Storage Contention [Article] Networking Performance Design [Article]
Read more
  • 0
  • 0
  • 16389

article-image-getting-started-with-chatgpt-advanced-data-analysis-part-2
Joshua Arvin Lat
08 Nov 2023
10 min read
Save for later

Getting Started with ChatGPT Advanced Data Analysis- Part 2

Joshua Arvin Lat
08 Nov 2023
10 min read
Dive deeper into the world of AI innovation and stay ahead of the AI curve! Subscribe to our AI_Distilled newsletter for the latest insights. Don't miss out – sign up today!IntroductionChatGPT Advanced Data Analysis is an invaluable tool that significantly speeds up the analysis and processing of our data and our files. In the first part of this post, we showcased how to use this feature to generate a CSV file containing randomly generated values. In addition to this, we demonstrated how to utilize different prompts in order to process data stored in files and generate visualizations. In this second part, we’ll build on top of what we learned already and work on more complex examples and scenarios.If you’re looking for the link to the first part, here it is: Getting Started with ChatGPT Advanced Data Analysis- Part 1That said, we will tackle the Example 03, Example 04, and Current Limitations sections in this post:Example 01 — Generating a CSV file (discussed in Part 1)Example 02 — Analyzing an uploaded CSV file, transforming the data, and generating charts  (discussed in Part 1)Example 03 — Processing and analyzing an iPython notebook fileExample 04 — Processing and analyzing the contents of a ZIP fileCurrent LimitationsWhile working on the hands-on examples in this post, you’ll be surprised that ChatGPT Advanced Data Analysis is able to process and analyze various types of files such as Jupyter Notebook (.ipynb) and even ZIP files containing multiple files. As you dive into the features of ChatGPT Advanced Data Analysis, you'll discover that it serves as a valuable addition to your data analysis toolkit. This will help you unlock various ways to optimize your workflow and significantly accelerate data-driven decision-making.Without further ado, let’s begin!Example 03: Processing and analyzing an iPython notebook fileIn this third example, we will process and analyze an iPython notebook file containing blocks of code for deploying machine learning models. Here, we’ll use an existing iPython notebook file I prepared while writing my 1st book “Machine Learning with Amazon SageMaker Cookbook”. If you're wondering what an iPython notebook file is, it's essentially a document produced by the Jupyter Notebook app, which contains both computer code (like Python) and rich text elements (paragraphs, equations, figures, links, etc.). These notebooks are both human-readable documents containing the analysis description and the results (like visualizations) as well as executable code that can be run to perform data analysis. It’s popular among data scientists and researchers for creating and sharing documents that contain live code, equations, visualizations, and narrative text.Now that we have a better idea of what iPython notebook (.ipynb) files are, let’s proceed with our 3rd example:STEP # 01: Open a new browser tab. Navigate to the following link and Download the .ipynb file to your local machine by clicking Download raw file:https://github.com/PacktPublishing/Machine-Learning-with-Amazon-SageMaker-Cookbook/blob/master/Chapter09/03%20-%20Hosting%20multiple%20models%20with%20multi-model%20endpoints.ipynbImage 17 — Downloading the IPython Notebook .ipynb fileTo download the file, simply click the download button highlighted in Image 17. This should download the .ipynb file to your local machine.STEP # 02: Navigate back to the browser tab where you have your ChatGPT session open and create a new chat session by clicking + New Chat. Make sure to select Advanced Data Analysis under the list of options available under GPT-4:Image 18 — Using Advanced Data AnalysisSTEP # 03: Upload the .ipynb file from your local machine to the new chat session and then run the following prompt:What's the ML instance type used in the example?This should yield the following response:Image 19 — Analyzing the uploaded file and identifying what ML instance type is used in the exampleIf you’re wondering what a machine learning (ML) instance is, you can think of it as a server or computer running specific machine learning workloads (such as training and serving machine learning models). Given that running these ML instances could be expensive, it’s best if we estimate the cost of running these instances!STEP # 04: Next, run the following prompt to locate the block of code where the ML instance type is mentioned or used:Print the code block(s) where this ML instance type is usedImage 20 — Locating the block of code where the ML instance type is usedCool, right? Here, we can see that ChatGPT can help us identify blocks of code using the right set of prompts. Make sure to verify the results and files produced by ChatGPT as you might find discrepancies or errors.STEP # 05: Run the following prompt to update the ML instance used in the previous block of code:Update the code block and use an ml.g5.2xlarge instead.Image 21 — Using ChatGPT to perform code modification instructionsHere, we can see that ChatGPT can easily perform code modification instructions as well. Note that ChatGPT is not limited to simply replacing certain portions of code blocks. It is also capable of generating code from scratch! In addition to this, it is capable of reading blocks of code as well.STEP # 06: Run the following prompt to generate a chart comparing the estimated cost per month when running ml.t2.medium and ml.g5.2xlarge inference endpoint instances:Generate a chart comparing the estimated cost per month when running an ml.t2.medium vs an ml.g5.2xlarge SageMaker inference endpoint instanceImage 22 — Estimated monthly cost comparison of running an ml.t2.medium instance vs ml.g5.2xlarge instanceMake sure to always verify the results and files produced by ChatGPT as you might find discrepancies or errors.Now, let’s proceed with our final example.Example 04: Processing and analyzing the contents of a ZIP fileIn this final example, we will compare the estimated cost per month of running the ML instances in each of the chapters of my book “Machine Learning with Amazon SageMaker Cookbook”. Of course, the assumption in this example is that we’ll be running the ML instances for an entire month. In reality, we’ll only be running these examples for a few seconds (to at most a few minutes).STEP # 01: Navigate to the following link:https://github.com/PacktPublishing/Machine-Learning-with-Amazon-SageMaker-CookbookSTEP # 02:Click Code and then click Download ZIP.Image 23 — Downloading the ZIP file containing the files of the repositoryThis will download a ZIP file containing all the files inside the repository to your local machine.STEP # 03: Create a new chat session by clicking + New Chat. Make sure to select Advanced Data Analysis under the list of options available under GPT-4:Image 24 — Choosing Advanced Data AnalysisSTEP # 04: Upload the downloaded Zip file from an earlier step to the new chat session (using the + button). Enter the following prompt to compare the estimated cost per month associated with running each of the examples per chapter:Analyze the contents of the ZIP file and perform the following: - for each of the directories, identify the ML instance types used - compare the estimated cost per month associated to running the ML instance types in the examples stored in each directory - group the estimated cost per month per chapter directoryThis should process the contents of the ZIP file we uploaded and yield the following response:Image 25 — Comparing the estimated cost per month per chapterWow, that seems expensive! Of course, we will NOT be running these resources for an entire month! In my first book “Machine Learning with Amazon SageMaker Cookbook”, the resources in each of the examples and recipes are only run for a few seconds to at most a few minutes and deleted almost right away. Since we only pay for what we use in Amazon Web Services (AWS), it should only cost a few dollars to complete all the examples in the book.Note that this example can be further improved by utilizing and uploading a spreadsheet with the actual price per hour of each of these instances. In addition to this, it is important to note that there are other cost factors not taken into account in this example as only the cost of running the instances are included. That said, we should also take into account the cost associated with the storage costs associated with the storage volumes attached to the instances, as well as the estimated charges for using other cloud services and resources in the account.STEP # 05: Finally, run the following prompt to compare the estimated monthly cost per chapter when running the examples of the book:Generate a bar chart comparing the estimated monthly cost per chapterThis should yield the following response:Image 26 — Bar chart comparing the estimated monthly cost for running the ML instance types per chapterCool, right? Here, we can see a bar chart that helps us compare the estimated monthly cost of running the examples in each chapter. Again, this is just for demonstration purposes as we will only be running the ML instances for a few seconds to at most a few minutes. This would mean that the actual cost would only be a tiny fraction of the overall monthly cost. In addition to this, it is important to note that there are other cost factors not taken into account in this example as only the cost of running the instances are included. Given that we’re just demonstrating the power of ChatGPT Advanced Data Analysis in this post, this simplified example should do the trick! Finally, make sure to always verify the results and files produced by ChatGPT as you might find discrepancies or errors.Current LimitationsBefore we end this tutorial, it is essential that we mention some of the current limitations (as of writing) when using ChatGPT Advanced Data Analysis. First, there is a file size limitation which restricts users to only uploading files up to a maximum size of 500 MB per file. This could affect those trying to analyze large datasets since they’ll be forced to divide larger files into smaller portions. In addition to this, ChatGPT retains uploaded files only during the active conversation and for an additional three hours after the conversation has been paused. Files are automatically deleted which would require users to re-upload the files to continue the analysis. Finally, we need to be aware that the execution of the instructions is done inside a sandboxed environment. This means that we are currently unable to have external integrations and perform real-time searches. Given that ChatGPT Advanced Data Analysis is still an experimental feature (that is, in Beta mode), there may still be a few limitations and issues being resolved behind the scenes. Of course, by the time you read this post, it may no longer be in Beta!That’s pretty much it. At this point, you should have a great idea on what you can accomplish using ChatGPT Advanced Data Analysis. Feel free to try different prompts and experiment with various scenarios to help you discover innovative ways to visualize and interpret your data for better decision-making.Author BioJoshua Arvin Lat is the Chief Technology Officer (CTO) of NuWorks Interactive Labs, Inc. He previously served as the CTO of 3 Australian-owned companies and also served as the Director for Software Development and Engineering for multiple e-commerce startups in the past. Years ago, he and his team won 1st place in a global cybersecurity competition with their published research paper. He is also an AWS Machine Learning Hero and he has been sharing his knowledge in several international conferences to discuss practical strategies on machine learning, engineering, security, and management. He is also the author of the books "Machine Learning with Amazon SageMaker Cookbook", "Machine Learning Engineering on AWS", and "Building and Automating Penetration Testing Labs in the Cloud". Due to his proven track record in leading digital transformation within organizations, he has been recognized as one of the prestigious Orange Boomerang: Digital Leader of the Year 2023 award winners.
Read more
  • 0
  • 0
  • 16362

article-image-android-native-application-api
Packt
13 May 2013
21 min read
Save for later

Android Native Application API

Packt
13 May 2013
21 min read
(For more resources related to this topic, see here.) Based on the features provided by the functions defined in these header files, the APIs can be grouped as follows: Activity lifecycle management: native_activity.h looper.h Windows management: rect.h window.h native_window.h native_window_jni.h Input (including key and motion events) and sensor events: input.h keycodes.h sensor.h Assets, configuration, and storage management: configuration.h asset_manager.h asset_manager_jni.h storage_manager.h obb.h In addition, Android NDK also provides a static library named native app glue to help create and manage native activities. The source code of this library can be found under the sources/android/native_app_glue/ directory. In this article, we will first introduce the creation of a native activity with the simple callback model provided by native_acitivity.h, and the more complicated but flexible two-threaded model enabled by the native app glue library. We will then discuss window management at Android NDK, where we will draw something on the screen from the native code. Input events handling and sensor accessing are introduced next. Lastly, we will introduce asset management, which manages the files under the assets folder of our project. Note that the APIs covered in this article can be used to get rid of the Java code completely, but we don't have to do so. The Managing assets at Android NDK recipe provides an example of using the asset management API in a mixed-code Android project. Before we start, it is important to keep in mind that although no Java code is needed in a native activity, the Android application still runs on Dalvik VM, and a lot of Android platform features are accessed through JNI. The Android native application API just hides the Java world for us. Creating a native activity with the native_activity.h interface The Android native application API allows us to create a native activity, which makes writing Android apps in pure native code possible. This recipe introduces how to write a simple Android application with pure C/C++ code. Getting ready Readers are expected to have basic understanding of how to invoke JNI functions. How to do it… The following steps to create a simple Android NDK application without a single line of Java code: Create an Android application named NativeActivityOne. Set the package name as cookbook.chapter5.nativeactivityone. Right-click on the NativeActivityOne project, select Android Tools | Add Native Support. Change the AndroidManifest.xml file as follows: <manifest package="cookbook.chapter5.nativeactivityone"android:versionCode="1"android:versionName="1.0"><uses-sdk android_minSdkVersion="9"/><application android_label="@string/app_name"android:icon="@drawable/ic_launcher"android:hasCode="true"><activity android_name="android.app.NativeActivity"android:label="@string/app_name"android:configChanges="orientation|keyboardHidden"><meta-data android_name="android.app.lib_name"android:value="NativeActivityOne" /><intent-filter><action android_name="android.intent.action.MAIN" /><category android_name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest> We should ensure that the following are set correctly in the preceding file: The activity name must be set to android.app.NativeActivity. The value of the android.app.lib_name metadata must be set to the native module name without the lib prefix and .so suffix. android:hasCode needs to be set to true, which indicates that the application contains code. Note that the documentation in <NDK root>/docs/NATIVE-ACTIVITY.HTML gives an example of the AndroidManifest.xml file with android:hasCode set to false, which will not allow the application to start. Add two files named NativeActivityOne.cpp and mylog.h under the jni folder. The ANativeActivity_onCreate method should be implemented in NativeActivityOne.cpp. The following is an example of the implementation: void ANativeActivity_onCreate(ANativeActivity* activity,void* savedState, size_t savedStateSize) {printInfo(activity);activity->callbacks->onStart = onStart;activity->callbacks->onResume = onResume;activity->callbacks->onSaveInstanceState = onSaveInstanceState;activity->callbacks->onPause = onPause;activity->callbacks->onStop = onStop;activity->callbacks->onDestroy = onDestroy;activity->callbacks->onWindowFocusChanged =onWindowFocusChanged;activity->callbacks->onNativeWindowCreated =onNativeWindowCreated;activity->callbacks->onNativeWindowResized =onNativeWindowResized;activity->callbacks->onNativeWindowRedrawNeeded =onNativeWindowRedrawNeeded;activity->callbacks->onNativeWindowDestroyed =onNativeWindowDestroyed;activity->callbacks->onInputQueueCreated = onInputQueueCreated;activity->callbacks->onInputQueueDestroyed =onInputQueueDestroyed;activity->callbacks->onContentRectChanged =onContentRectChanged;activity->callbacks->onConfigurationChanged =onConfigurationChanged;activity->callbacks->onLowMemory = onLowMemory;activity->instance = NULL;} Add the Android.mk file under the jni folder: LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := NativeActivityOneLOCAL_SRC_FILES := NativeActivityOne.cppLOCAL_LDLIBS := -landroid -lloginclude $(BUILD_SHARED_LIBRARY) Build the Android application and run it on an emulator or a device. Start a terminal and display the logcat output using the following: $ adb logcat -v time NativeActivityOne:I *:S Alternatively, you can use the logcat view at Eclipse to see the logcat output. When the application starts, you should be able to see the following logcat output: As shown in the screenshot, a few Android activity lifecycle callback functions are executed. We can manipulate the phone to cause other callbacks being executed. For example, long pressing the home button and then pressing the back button will cause the onWindowFocusChanged callback to be executed. How it works… In our example, we created a simple, "pure" native application to output logs when the Android framework calls into the callback functions defined by us. The "pure" native application is not really pure native. Although we did not write a single line of Java code, the Android framework still runs some Java code on Dalvik VM. Android framework provides an android.app.NativeActivity.java class to help us create a "native" activity. In a typical Java activity, we extend android.app.Activity and overwrite the activity lifecycle methods. NativeActivity is also a subclass of android. app.Activity and does similar things. At the start of a native activity, NativeActivity. java will call ANativeActivity_onCreate, which is declared in native_activity.h and implemented by us. In the ANativeActivity_onCreate method, we can register our callback methods to handle activity lifecycle events and user inputs. At runtime, NativeActivity will invoke these native callback methods when the corresponding events occurred. In a word, NativeActivity is a wrapper that hides the managed Android Java world for our native code, and exposes the native interfaces defined in native_activity.h. The ANativeActivity data structure: Every callback method in the native code accepts an instance of the ANativeActivity structure. Android NDK defines the ANativeActivity data structure in native_acitivity.h as follows: typedef struct ANativeActivity {struct ANativeActivityCallbacks* callbacks;JavaVM* vm;JNIEnv* env;jobject clazz;const char* internalDataPath;const char* externalDataPath;int32_t sdkVersion;void* instance;AAssetManager* assetManager;} ANativeActivity; The various attributes of the preceding code are explained as follows: callbacks: It is a data structure that defines all the callbacks that the Android framework will invoke with the main UI thread. vm: It is the application process' global Java VM handle. It is used in some JNI functions. env: It is a JNIEnv interface pointer. JNIEnv is used through local storage data , so this field is only accessible through the main UI thread. clazz: It is a reference to the android.app.NativeActivity object created by the Android framework. It can be used to access fields and methods in the android. app.NativeActivity Java class. In our code, we accessed the toString method of android.app.NativeActivity. internalDataPath: It is the internal data directory path for the application. externalDataPath: It is the external data directory path for the application. internalDataPath and externalDataPath are NULL at Android 2.3.x. This is a known bug and has been fixed since Android 3.0. If we are targeting devices lower than Android 3.0, then we need to find other ways to get the internal and external data directories. sdkVersion: It is the Android platform's SDK version code. Note that this refers to the version of the device/emulator that runs the app, not the SDK version used in our development. instance: It is not used by the framework. We can use it to store user-defined data and pass it around. assetManager: It is the a pointer to the app's instance of the asset manager. We will need it to access assets data. We will discuss it in more detail in the Managing assets at Android NDK recipe of this article There's more… The native_activity.h interface provides a simple single thread callback mechanism, which allows us to write an activity without Java code. However, this single thread approach infers that we must quickly return from our native callback methods. Otherwise, the application will become unresponsive to user actions (for example, when we touch the screen or press the Menu button, the app does not respond because the GUI thread is busy executing the callback function). A way to solve this issue is to use multiple threads. For example, many games take a few seconds to load. We will need to offload the loading to a background thread, so that the UI can display the loading progress and be responsive to user inputs. Android NDK comes with a static library named android_native_app_glue to help us in handling such cases. The details of this library are covered in the Creating a native activity with the Android native app glue recipe. A similar problem exists at Java activity. For example, if we write a Java activity that searches the entire device for pictures at onCreate, the application will become unresponsive. We can use AsyncTask to search and load pictures in the background, and let the main UI thread display a progress bar and respond to user inputs. Creating a native activity with the Android native app glue The previous recipe described how the interface defined in native_activity.h allows us to create native activity. However, all the callbacks defined are invoked with the main UI thread, which means we cannot do heavy processing in the callbacks. Android SDK provides AsyncTask, Handler, Runnable, Thread, and so on, to help us handle things in the background and communicate with the main UI thread. Android NDK provides a static library named android_native_app_glue to help us execute callback functions and handle user inputs in a separate thread. This recipe will discuss the android_native_app_glue library in detail. Getting ready The android_native_app_glue library is built on top of the native_activity.h interface. Therefore, readers are recommended to read the Creating a native activity with the native_activity.h interface recipe before going through this one. How to do it… The following steps create a simple Android NDK application based on the android_native_app_glue library: Create an Android application named NativeActivityTwo. Set the package name as cookbook.chapter5.nativeactivitytwo. Right-click on the NativeActivityTwo project, select Android Tools | Add Native Support. Change the AndroidManifest.xml file as follows: <manifest package="cookbook.chapter5.nativeactivitytwo"android:versionCode="1"android:versionName="1.0"><uses-sdk android_minSdkVersion="9"/><application android_label="@string/app_name"android:icon="@drawable/ic_launcher"android:hasCode="true"><activity android_name="android.app.NativeActivity"android:label="@string/app_name"android:configChanges="orientation|keyboardHidden"><meta-data android_name="android.app.lib_name"android:value="NativeActivityTwo" /><intent-filter><action android_name="android.intent.action.MAIN" /><category android_name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest> Add two files named NativeActivityTwo.cpp and mylog.h under the jni folder. NativeActivityTwo.cpp is shown as follows: #include <jni.h>#include <android_native_app_glue.h>#include "mylog.h"void handle_activity_lifecycle_events(struct android_app* app,int32_t cmd) {LOGI(2, "%d: dummy data %d", cmd, *((int*)(app->userData)));}void android_main(struct android_app* app) {app_dummy(); // Make sure glue isn't stripped.int dummyData = 111;app->userData = &dummyData;app->onAppCmd = handle_activity_lifecycle_events;while (1) {int ident, events;struct android_poll_source* source;if ((ident=ALooper_pollAll(-1, NULL, &events, (void**)&source)) >=0) {source->process(app, source);}}} Add the Android.mk file under the jni folder: LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := NativeActivityTwoLOCAL_SRC_FILES := NativeActivityTwo.cppLOCAL_LDLIBS := -llog -landroidLOCAL_STATIC_LIBRARIES := android_native_app_glueinclude $(BUILD_SHARED_LIBRARY)$(call import-module,android/native_app_glue) Build the Android application and run it on an emulator or device. Start a terminal and display the logcat output by using the following command: adb logcat -v time NativeActivityTwo:I *:S When the application starts, you should be able to see the following logcat output and the device screen will shows a black screen: On pressing the back button, the following output will be shown: How it works… This recipe demonstrates how the android_native_app_glue library is used to create a native activity. The following steps should be followed to use the android_native_app_glue library: Implement a function named android_main. This function should implement an event loop, which will poll for events continuously. This method will run in the background thread created by the library. Two event queues are attached to the background thread by default, including the activity lifecycle event queue and the input event queue. When polling events using the looper created by the library, you can identify where the event is coming from, by checking the returned identifier (either LOOPER_ID_MAIN or LOOPER_ID_INPUT). It is also possible to attach additional event queues to the background thread. When an event is returned, the data pointer will point to an android_poll_source data structure. We can call the process function of this structure. The process is a function pointer, which points to android_app->onAppCmd for activity lifecycle events, and android_app->onInputEvent for input events. We can provide our own processing functions and direct the corresponding function pointers to these functions. In our example, we implement a simple function named handle_activity_lifecycle_ events and point the android_app->onAppCmd function pointer to it. This function simply prints the cmd value and the user data passed along with the android_app data structure. cmd is defined in android_native_app_glue.h as an enum. For example, when the app starts, the cmd values are 10, 11, 0, 1, and 6, which correspond to APP_CMD_START, APP_CMD_RESUME, APP_CMD_INPUT_CHANGED, APP_CMD_INIT_WINDOW, and APP_CMD_ GAINED_FOCUS respectively. android_native_app_glue Library Internals: The source code of the android_native_ app_glue library can be found under the sources/android/native_app_glue folder of Android NDK. It only consists of two files, namely android_native_app_glue.c and android_native_app_glue.h. Let's first describe the flow of the code and then discuss some important aspects in detail. Since the source code for native_app_glue is provided, we can modify it if necessary, although in most cases it won't be necessary. android_native_app_glue is built on top of the native_activity.h interface. As shown in the following code (extracted from sources/android/native_app_glue/ android_native_app_glue.c). It implements the ANativeActivity_onCreate function, where it registers the callback functions and calls the android_app_create function. Note that the returned android_app instance is pointed by the instance field of the native activity, which can be passed to various callback functions: void ANativeActivity_onCreate(ANativeActivity* activity,void* savedState, size_t savedStateSize) {LOGV("Creating: %pn", activity);activity->callbacks->onDestroy = onDestroy;activity->callbacks->onStart = onStart;activity->callbacks->onResume = onResume;… …activity->callbacks->onNativeWindowCreated =onNativeWindowCreated;activity->callbacks->onNativeWindowDestroyed =onNativeWindowDestroyed;activity->callbacks->onInputQueueCreated = onInputQueueCreated;activity->callbacks->onInputQueueDestroyed =onInputQueueDestroyed;activity->instance = android_app_create(activity, savedState,savedStateSize);} The android_app_create function (shown in the following code snippet) initializes an instance of the android_app data structure, which is defined in android_native_app_ glue.h. This function creates a unidirectional pipe for inter-thread communication. After that, it spawns a new thread (let's call it background thread thereafter) to run the android_ app_entry function with the initialized android_app data as the input argument. The main thread will wait for the background thread to start and then return: static struct android_app* android_app_create(ANativeActivity*activity, void* savedState, size_t savedStateSize) {struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app));memset(android_app, 0, sizeof(struct android_app));android_app->activity = activity;pthread_mutex_init(&android_app->mutex, NULL);pthread_cond_init(&android_app->cond, NULL);……int msgpipe[2];if (pipe(msgpipe)) {LOGE("could not create pipe: %s", strerror(errno));return NULL;}android_app->msgread = msgpipe[0];android_app->msgwrite = msgpipe[1];pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_create(&android_app->thread, &attr, android_app_entry,android_app);// Wait for thread to start.pthread_mutex_lock(&android_app->mutex);while (!android_app->running) {pthread_cond_wait(&android_app->cond, &android_app->mutex);}pthread_mutex_unlock(&android_app->mutex);return android_app;} The background thread starts with the android_app_entry function (as shown in the following code snippet), where a looper is created. Two event queues will be attached to the looper. The activity lifecycle events queue is attached to the android_app_entry function. When the activity's input queue is created, the input queue is attached (to the android_ app_pre_exec_cmd function of android_native_app_glue.c). After attaching the activity lifecycle event queue, the background thread signals the main thread it is already running. It then calls a function named android_main with the android_app data. android_main is the function we need to implement, as shown in our sample code. It must run in a loop until the activity exits: static void* android_app_entry(void* param) {struct android_app* android_app = (struct android_app*)param;… …//Attach life cycle event queue with identifier LOOPER_ID_MAINandroid_app->cmdPollSource.id = LOOPER_ID_MAIN;android_app->cmdPollSource.app = android_app;android_app->cmdPollSource.process = process_cmd;android_app->inputPollSource.id = LOOPER_ID_INPUT;android_app->inputPollSource.app = android_app;android_app->inputPollSource.process = process_input;ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN,ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);android_app->looper = looper;pthread_mutex_lock(&android_app->mutex);android_app->running = 1;pthread_cond_broadcast(&android_app->cond);pthread_mutex_unlock(&android_app->mutex);android_main(android_app);android_app_destroy(android_app);return NULL;} The following diagram indicates how the main and background thread work together to create the multi-threaded native activity: We use the activity lifecycle event queue as an example. The main thread invokes the callback functions, which simply writes to the write end of the pipe, while true loop implemented in the android_main function will poll for events. Once an event is detected, the function calls the event handler, which reads the exact command from the read end of the pipe and handles it. The android_native_app_glue library implements all the main thread stuff and part of the background thread stuff for us. We only need to supply the polling loop and the event handler as illustrated in our sample code. Pipe: The main thread creates a unidirectional pipe in the android_app_create function by calling the pipe method. This method accepts an array of two integers. After the function is returned, the first integer will be set as the file descriptor referring to the read end of the pipe, while the second integer will be set as the file descriptor referring to the write end of the pipe. A pipe is usually used for Inter-process Communication (IPC), but here it is used for communication between the main UI thread and the background thread created at android_ app_entry. When an activity lifecycle event occurs, the main thread will execute the corresponding callback function registered at ANativeActivity_onCreate. The callback function simply writes a command to the write end of the pipe and then waits for a signal from the background thread. The background thread is supposed to poll for events continuously and once it detects a lifecycle event, it will read the exact event from the read end of the pipe, signal the main thread to unblock and handle the events. Because the signal is sent right after receiving the command and before actual processing of the events, the main thread can return from the callback function quickly without worrying about the possible long processing of the events. Different operating systems have different implementations for the pipe. The pipe implemented by Android system is "half-duplex", where communication is unidirectional. That is, one file descriptor can only write, and the other file descriptor can only read. Pipes in some operating system is "full-duplex", where the two file descriptors can both read and write. Looper is an event tracking facility, which allows us to attach one or more event queues for an event loop of a thread. Each event queue has an associated file descriptor. An event is data available on a file descriptor. In order to use a looper, we need to include the android/ looper.h header file. The library attaches two event queues for the event loop to be created by us in the background thread, including the activity lifecycle event queue and the input event queue. The following steps should be performed in order to use a looper: Create or obtain a looper associated with the current thread: This is done by the ALooper_prepare function: ALooper* ALooper_prepare(int opts); This function prepares a looper associated with the calling thread and returns it. If the looper doesn't exist, it creates one, associates it with the thread, and returns it Attach an event queue: This is done by ALooper_addFd. The function has the following prototype: int ALooper_addFd(ALooper* looper, int fd, int ident, int events,ALooper_callbackFunc callback, void* data); The function can be used in two ways. Firstly, if callback is set to NULL, the ident set will be returned by ALooper_pollOnce and ALooper_pollAll. Secondly, if callback is non-NULL, then the callback function will be executed and ident is ignored. The android_native_app_glue library uses the first approach to attach a new event queue to the looper. The input argument fd indicates the file descriptor associated with the event queue. ident is the identifier for the events from the event queue, which can be used to classify the event. The identifier must be bigger than zero when callback is set to NULL. callback is set to NULL in the library source code, and data points to the private data that will be returned along with the identifier at polling. In the library, this function is called to attach the activity lifecycle event queue to the background thread. The input event queue is attached using the input queue specific function AInputQueue_attachLooper, which we will discuss in the Detecting and handling input events at NDK recipe. Poll for events: This can be done by either one of the following two functions: int ALooper_pollOnce(int timeoutMillis, int* outFd, int*outEvents, void** outData);int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents,void** outData); These two methods are equivalent when callback is set to NULL in ALooper_addFd. They have the same input arguments. timeoutMillis specifies the timeout for polling. If it is set to zero, then the functions return immediately; if it is set to negative, they will wait indefinitely until an event occurs. The functions return the identifier (greater than zero) when an event occurs from any input queues attached to the looper. In this case, outFd, outEvents, and outData will be set to the file descriptor, poll events, and data associated with the event. Otherwise, they will be set to NULL. Detach event queues: This is done by the following function: int ALooper_removeFd(ALooper* looper, int fd); It accepts the looper and file descriptor associated with the event queue, and detaches the queue from the looper.
Read more
  • 0
  • 0
  • 16359

article-image-data-pipelines
Packt
03 Mar 2017
17 min read
Save for later

Data Pipelines

Packt
03 Mar 2017
17 min read
In this article by Andrew Morgan, Antoine Amend, Matthew Hallett, David George, the author of the book Mastering Spark for Data Science, readers will learn how to construct a content registerand use it to track all input loaded to the system, and to deliver metrics on ingestion pipelines, so that these flows can be reliably run as an automated, lights-out process. Readers will learn how to construct a content registerand use it to track all input loaded to the system, and to deliver metrics on ingestion pipelines, so that these flows can be reliably run as an automated, lights-out process. In this article we will cover the following topics: Welcome the GDELT Dataset Data Pipelines Universal Ingestion Framework Real-time monitoring for new data Receiving Streaming Data via Kafka Registering new content and vaulting for tracking purposes Visualization of content metrics in Kibana - to monitor ingestion processes & data health   (For more resources related to this topic, see here.) Data Pipelines Even with the most basic of analytics, we always require some data. In fact, finding the right data is probably among the hardest problems to solve in data science (but that’s a whole topic for another book!). We have already seen that the way in which we obtain our data can be as simple or complicated as is needed. In practice, we can break this decision into two distinct areas: Ad-hoc and scheduled. Ad-hoc data acquisition is the most common method during prototyping and small scale analytics as it usually doesn’t require any additional software to implement - the user requires some data and simply downloads it from source as and when required. This method is often a matter of clicking on a web link and storing the data somewhere convenient, although the data may still need to be versioned and secure. Scheduled data acquisition is used in more controlled environments for large scale and production analytics, there is also an excellent case for ingesting a dataset into a data lake for possible future use. With Internet of Things (IoT) on the increase, huge volumes of data are being produced, in many cases if the data is not ingested now it is lost forever. Much of this data may not have an immediate or apparent use today, but could do in the future; so the mind-set is to gather all of the data in case it is needed and delete it later when sure it is not. It’s clear we need a flexible approach to data acquisition that supports a variety of procurement options. Universal Ingestion Framework There are many ways to approach data acquisition ranging from home grown bash scripts through to high-end commercial tools. The aim of this section is to introduce a highly flexible framework that we can use for small scale data ingest, and then grow as our requirements change - all the way through to a full corporately managed workflow if needed - that framework will be build using Apache NiFi. NiFi enables us to build large-scale integrated data pipelines that move data around the planet. In addition, it’s also incredibly flexible and easy to build simple pipelines - usually quicker even than using Bash or any other traditional scripting method. If an ad-hoc approach is taken to source the same dataset on a number of occasions, then some serious thought should be given as to whether it falls into the scheduled category, or at least whether a more robust storage and versioning setup should be introduced. We have chosen to use Apache NiFi as it offers a solution that provides the ability to create many, varied complexity pipelines that can be scaled to truly Big Data and IoT levels, and it also provides a great drag & drop interface (using what’s known as flow-based programming[1]). With patterns, templates and modules for workflow production, it automatically takes care of many of the complex features that traditionally plague developers such as multi-threading, connection management and scalable processing. For our purposes it will enable us to quickly build simple pipelines for prototyping, and scale these to full production where required. It’s pretty well documented and easy to get running https://nifi.apache.org/download.html, it runs in a browser and looks like this: https://en.wikipedia.org/wiki/Flow-based_programming We leave the installation of NiFi as an exercise for the reader - which we would encourage you to do - as we will be using it in the following section. Introducing the GDELT News Stream Hopefully, we have NiFi up and running now and can start to ingest some data. So let’s start with some global news media data from GDELT. Here’s our brief, taken from the GDELT website http://blog.gdeltproject.org/gdelt-2-0-our-global-world-in-realtime/: “Within 15 minutes of GDELT monitoring a news report breaking anywhere the world, it has translated it, processed it to identify all events, counts, quotes, people, organizations, locations, themes, emotions, relevant imagery, video, and embedded social media posts, placed it into global context, and made all of this available via a live open metadata firehose enabling open research on the planet itself. [As] the single largest deployment in the world of sentiment analysis, we hope that by bringing together so many emotional and thematic dimensions crossing so many languages and disciplines, and applying all of it in realtime to breaking news from across the planet, that this will spur an entirely new era in how we think about emotion and the ways in which it can help us better understand how we contextualize, interpret, respond to, and understand global events.” In order to start consuming this open data, we’ll need to hook into that metadata firehose and ingest the news streams onto our platform.  How do we do this?  Let’s start by finding out what data is available. Discover GDELT Real-time GDELT publish a list of the latest files on their website - this list is updated every 15 minutes. In NiFi, we can setup a dataflow that will poll the GDELT website, source a file from this list and save it to HDFS so we can use it later. Inside the NiFi dataflow designer, create a HTTP connector by dragging a processor onto the canvas and selecting GetHTTP. To configure this processor, you’ll need to enter the URL of the file list as: http://data.gdeltproject.org/gdeltv2/lastupdate.txt And also provide a temporary filename for the file list you will download. In the example below, we’ve used the NiFi’s expression language to generate a universally unique key so that files are not overwritten (UUID()). It’s worth noting that with this type of processor (GetHTTP), NiFi supports a number of scheduling and timing options for the polling and retrieval. For now, we’re just going to use the default options and let NiFi manage the polling intervals for us. An example of latest file list from GDELT is shown below. Next, we will parse the URL of the GKG news stream so that we can fetch it in a moment. Create a Regular Expression parser by dragging a processor onto the canvas and selecting ExtractText. Now position the new processor underneath the existing one and drag a line from the top processor to the bottom one. Finish by selecting the success relationship in the connection dialog that pops up. This is shown in the example below. Next, let’s configure the ExtractText processor to use a regular expression that matches only the relevant text of the file list, for example: ([^ ]*gkg.csv.*) From this regular expression, NiFi will create a new property (in this case, called url) associated with the flow design, which will take on a new value as each particular instance goes through the flow. It can even be configured to support multiple threads. Again, this is example is shown below. It’s worth noting here that while this is a fairly specific example, the technique is deliberately general purpose and can be used in many situations. Our First GDELT Feed Now that we have the URL of the GKG feed, we fetch it by configuring an InvokeHTTP processor to use the url property we previously created as it’s remote endpoint, and dragging the line as before. All that remains is to decompress the zipped content with a UnpackContent processor (using the basic zip format) and save to HDFS using a PutHDFS processor, like so: Improving with Publish and Subscribe So far, this flow looks very “point-to-point”, meaning that if we were to introduce a new consumer of data, for example, a Spark-streaming job, the flow must be changed. For example, the flow design might have to change to look like this: If we add yet another, the flow must change again. In fact, each time we add a new consumer, the flow gets a little more complicated, particularly when all the error handling is added. This is clearly not always desirable, as introducing or removing consumers (or producers) of data, might be something we want to do often, even frequently. Plus, it’s also a good idea to try to keep your flows as simple and reusable as possible. Therefore, for a more flexible pattern, instead of writing directly to HDFS, we can publish to Apache Kafka. This gives us the ability to add and remove consumers at any time without changing the data ingestion pipeline. We can also still write to HDFS from Kafka if needed, possibly even by designing a separate NiFi flow, or connect directly to Kafka using Spark-streaming. To do this, we create a Kafka writer by dragging a processor onto the canvas and selecting PutKafka. We now have a simple flow that continuously polls for an available file list, routinely retrieving the latest copy of a new stream over the web as it becomes available, decompressing the content and streaming it record-by-record into Kafka, a durable, fault-tolerant, distributed message queue, for processing by spark-streaming or storage in HDFS. And what’s more, without writing a single line of bash! Content Registry We have seen in this article that data ingestion is an area that is often overlooked, and that its importance cannot be underestimated. At this point we have a pipeline that enables us to ingest data from a source, schedule that ingest and direct the data to our repository of choice. But the story does not end there. Now we have the data, we need to fulfil our data management responsibilities. Enter the content registry. We’re going to build an index of metadata related to that data we have ingested. The data itself will still be directed to storage (HDFS, in our example) but, in addition, we will store metadata about the data, so that we can track what we’ve received and understand basic information about it, such as, when we received it, where it came from, how big it is, what type it is, etc. Choices and More Choices The choice of which technology we use to store this metadata is, as we have seen, one based upon knowledge and experience. For metadata indexing, we will require at least the following attributes: Easily searchable Scalable Parallel write ability Redundancy There are many ways to meet these requirements, for example we could write the metadata to Parquet, store in HDFS and search using Spark SQL. However, here we will use Elasticsearch as it meets the requirements a little better, most notably because it facilitates low latency queries of our metadata over a REST API - very useful for creating dashboards. In fact, Elasticsearch has the advantage of integrating directly with Kibana, meaning it can quickly produce rich visualizations of our content registry. For this reason, we will proceed with Elasticsearch in mind. Going with the Flow Using our current NiFi pipeline flow, let’s fork the output from “Fetch GKG files from URL” to add an additional set of steps to allow us to capture and store this metadata in Elasticsearch. These are: Replace the flow content with our metadata model Capture the metadata Store directly in Elasticsearch Here’s what this looks like in NiFi: Metadata Model So, the first step here is to define our metadata model. And there are many areas we could consider, but let’s select a set that helps tackle a few key points from earlier discussions. This will provide a good basis upon which further data can be added in the future, if required. So, let’s keep it simple and use the following three attributes: File size Date ingested File name These will provide basic registration of received files. Next, inside the NiFi flow, we’ll need to replace the actual data content with this new metadata model. An easy way to do this, is to create a JSON template file from our model. We’ll save it to local disk and use it inside a FetchFile processor to replace the flow’s content with this skeleton object. This template will look something like: { "FileSize": SIZE, "FileName": "FILENAME", "IngestedDate": "DATE" } Note the use of placeholder names (SIZE, FILENAME, DATE) in place of the attribute values. These will be substituted, one-by-one, by a sequence of ReplaceText processors, that swap the placeholder names for an appropriate flow attribute using regular expressions provided by the NiFi Expression Language, for example DATE becomes ${now()}. The last step is to output the new metadata payload to Elasticsearch. Once again, NiFi comes ready with a processor for this; the PutElasticsearch processor. An example metadata entry in Elasticsearch: { "_index": "gkg", "_type": "files", "_id": "AVZHCvGIV6x-JwdgvCzW", "_score": 1, "source": { "FileSize": 11279827, "FileName": "20150218233000.gkg.csv.zip", "IngestedDate": "2016-08-01T17:43:00+01:00" } } Now that we have added the ability to collect and interrogate metadata, we now have access to more statistics that can be used for analysis. This includes: Time based analysis e.g. file sizes over time Loss of data, for example are there data “holes” in the timeline? If there is a particular analytic that is required, the NIFI metadata component can be adjusted to provide the relevant data points. Indeed, an analytic could be built to look at historical data and update the index accordingly if the metadata does not exist in current data. Kibana Dashboard We have mentioned Kibana a number of times in this article, now that we have an index of metadata in Elasticsearch, we can use the tool to visualize some analytics. The purpose of this brief section is to demonstrate that we can immediately start to model and visualize our data. In this simple example we have completed the following steps: Added the Elasticsearch index for our GDELT metadata to the “Settings” tab Selected “file size” under the “Discover” tab Selected Visualize for “file size” Changed the Aggregation field to “Range” Entered values for the ranges The resultant graph displays the file size distribution: From here we are free to create new visualizations or even a fully featured dashboard that can be used to monitor the status of our file ingest. By increasing the variety of metadata written to Elasticsearch from NiFi, we can make more fields available in Kibana and even start our data science journey right here with some ingest based actionable insights. Now that we have a fully-functioning data pipeline delivering us real-time feeds of data, how do we ensure data quality of the payload we are receiving?  Let’s take a look at the options. Quality Assurance With an initial data ingestion capability implemented, and data streaming onto your platform, you will need to decide how much quality assurance is required at the front door. It’s perfectly viable to start with no initial quality controls and build them up over time (retrospectively scanning historical data as time and resources allow). However, it may be prudent to install a basic level of verification to begin with. For example, basic checks such as file integrity, parity checking, completeness, checksums, type checking, field counting, overdue files, security field pre-population, denormalization, etc. You should take care that your up-front checks do not take too long. Depending on the intensity of your examinations and the size of your data, it’s not uncommon to encounter a situation where there is not enough time to perform all processing before the next dataset arrives. You will always need to monitor your cluster resources and calculate the most efficient use of time. Here are some examples of the type of rough capacity planning calculation you can perform: Example 1: Basic Quality Checking, No Contending Users Data is ingested every 15 minutes and takes 1 minute to pull from the source Quality checking (integrity, field count, field pre-population) takes 4 minutes There are no other users on the compute cluster There are 10 minutes of resources available for other tasks. As there are no other users on the cluster, this is satisfactory - no action needs to be taken. Example 2: Advanced Quality Checking, No Contending Users Data is ingested every 15 minutes and takes 1 minute to pull from the source Quality checking (integrity, field count, field pre-population, denormalization, sub dataset building) takes 13 minutes There are no other users on the compute cluster There is only 1 minute of resource available for other tasks. We probably need to consider, either: Configuring a resource scheduling policy Reducing the amount of data ingested Reducing the amount of processing we undertake Adding additional compute resources to the cluster Example 3: Basic Quality Checking, 50% Utility Due to Contending Users Data is ingested every 15 minutes and takes 1 minute to pull from the source Quality checking (integrity, field count, field pre-population) takes 4 minutes (100% utility) There are other users on the compute cluster There are 6 minutes of resources available for other tasks (15 - 1 - (4 * (100 / 50))). Since there are other users there is a danger that, at least some of the time, we will not be able to complete our processing and a backlog of jobs will occur. When you run into timing issues, you have a number of options available to you in order to circumvent any backlog: Negotiating sole use of the resources at certain times Configuring a resource scheduling policy, including: YARN Fair Scheduler: allows you to define queues with differing priorities and target your Spark jobs by setting the spark.yarn.queue property on start-up so your job always takes precedence Dynamicandr Resource Allocation: allows concurrently running jobs to automatically scale to match their utilization Spark Scheduler Pool: allows you to define queues when sharing a SparkContext using multithreading model, and target your Spark job by setting the spark.scheduler.pool property per execution thread so your thread takes precedence Running processing jobs overnight when the cluster is quiet In any case, you will eventually get a good idea of how the various parts to your jobs perform and will then be in a position to calculate what changes could be made to improve efficiency. There’s always the option of throwing more resources at the problem, especially when using a cloud provider, but we would certainly encourage the intelligent use of existing resources - this is far more scalable, cheaper and builds data expertise. Summary In this article we walked through the full setup of an Apache NiFi GDELT ingest pipeline, complete with metadata forks and a brief introduction to visualizing the resultant data. This section is particularly important as GDELT is used extensively throughout the book and the NiFi method is a highly effective way to source data in a scalable and modular way. Resources for Article: Further resources on this subject: Integration with Continuous Delivery [article] Amazon Web Services [article] AWS Fundamentals [article]
Read more
  • 0
  • 1
  • 16359

article-image-sentiment-analysis-with-generative-ai
Sangita Mahala
09 Nov 2023
8 min read
Save for later

Sentiment Analysis with Generative AI

Sangita Mahala
09 Nov 2023
8 min read
Dive deeper into the world of AI innovation and stay ahead of the AI curve! Subscribe to our AI_Distilled newsletter for the latest insights. Don't miss out – sign up today!IntroductionThe process of detecting and extracting emotion from text is referred to as sentiment analysis. It's a powerful tool that can help understand the views of consumers, look at brand ratings, and find satisfaction with customers. Genetic AI models like GPT 3, PaLM, and Bard can change the way we think about sentiment analysis. It is possible to train these models with the aim of understanding human language's nuances, and detecting sentiment in any complicated or subtle text.Benefits of using generative AI for sentiment analysisThere are several benefits to using generative AI for sentiment analysis, including:Accuracy: In the area of sentiment analysis, neural AI models are capable of achieving very high accuracy. It is because of their ability to learn the intricate patterns and relationships between words and phrases which have different meanings.Scalability: Generative AI models can be scaled to analyze large volumes of text quickly and efficiently. It is of particular importance for businesses and organizations that need to process large quantities of feedback from customers or are dealing with Social Media data.Flexibility: In order to take into account the specific needs of different companies and organizations, genetic AI models may be adapted. A model may be trained to determine the feelings of customer assessments, Social Media posts, or any other type of news.How to use generative AI for sentiment analysisThere are two main ways to use generative AI for sentiment analysis:Prompt engineering: Prompt engineering is the process of designing prompts, which are used to guide generative AI models in generating desired outputs. For example, the model might be asked to state "the following sentence as positive, negative, or neutral: I'm in love with this new product!"Fine-tuning: Finetuning refers to a process of training the generative AI model on some particular text and label data set. This is why the model will be able to discover special patterns and relationships associated with various emotions in that data set.Hands-on examplesIn this example, we will use the PaLM API to perform sentiment analysis on a customer review.Example - 1Input:import nltk from nltk.sentiment.vader import SentimentIntensityAnalyzer # Download the VADER lexicon for sentiment analysis (run this once) nltk.download('vader_lexicon') def analyze_sentiment(sentence):    # Initialize the VADER sentiment intensity analyzer    analyzer = SentimentIntensityAnalyzer()      # Analyze the sentiment of the sentence    sentiment_scores = analyzer.polarity_scores(sentence)      # Determine the sentiment based on the compound score    if sentiment_scores['compound'] >= 0.05:        return 'positive'    elif sentiment_scores['compound'] <= -0.05:        return 'negative'    else:        return 'neutral' # Example usage with a positive sentence positive_sentence = "I am thrilled with the results! The team did an amazing job!" sentiment = analyze_sentiment(positive_sentence) print(f"Sentiment: {sentiment}")Output:Sentiment: positiveIn order to analyze the emotion in a particular sentence we have created a function that divides it into categories based on its sentiment score and labels it as positive, unfavorable, or neutral. For example, a positive sentence is analyzed and the results show a "positive" sentiment.Example - 2Input:import nltk from nltk.sentiment.vader import SentimentIntensityAnalyzer # Download the VADER lexicon for sentiment analysis (run this once) nltk.download('vader_lexicon') def analyze_sentiment(sentence):    # Initialize the VADER sentiment intensity analyzer    analyzer = SentimentIntensityAnalyzer()      # Analyze the sentiment of the sentence    sentiment_scores = analyzer.polarity_scores(sentence)      # Determine the sentiment based on the compound score    if sentiment_scores['compound'] >= 0.05:        return 'positive'    elif sentiment_scores['compound'] <= -0.05:        return 'negative'    else:        return 'neutral' # Example usage with a negative sentence negative_sentence = "I am very disappointed with the service. The product didn't meet my expectations." sentiment = analyze_sentiment(negative_sentence) print(f"Sentiment: {sentiment}")Output:Sentiment: negativeWe have set up a function to evaluate the opinions of some sentences and then classify them according to their opinion score, which we refer to as Positive, Negative, or Neutral. To illustrate this point, an analysis of the negative sentence is carried out and the output indicates a "negative" sentiment.Example - 3Input:import nltk from nltk.sentiment.vader import SentimentIntensityAnalyzer # Download the VADER lexicon for sentiment analysis (run this once) nltk.download('vader_lexicon') def analyze_sentiment(sentence):    # Initialize the VADER sentiment intensity analyzer    analyzer = SentimentIntensityAnalyzer()    # Analyze the sentiment of the sentence    sentiment_scores = analyzer.polarity_scores(sentence)    # Determine the sentiment based on the compound score    if sentiment_scores['compound'] >= 0.05:        return 'positive'    elif sentiment_scores['compound'] <= -0.05:        return 'negative'    else:        return 'neutral' # Example usage sentence = "This is a neutral sentence without any strong sentiment." sentiment = analyze_sentiment(sentence) print(f"Sentiment: {sentiment}")Output:Sentiment: neutralFor every text item, whether it's a customer review, social media post or news report, the PaLM API can be used for sentiment analysis. To do this, select a prompt that tells your model what it wants to be doing and then request API by typing in the following sentence as positive, negative, or neutral. A prediction that you can print to the console or use in your application will then be generated by this model.Applications of sentiment analysis with generative AISentiment analysis with generative AI can be used in a wide variety of applications, including:Customer feedback analysis: Generative AI models can be used to analyze customer reviews and feedback to identify trends and areas for improvement.Social media monitoring: Generative AI models can be used to monitor social media platforms for brand sentiment and public opinion.Market research: In order to get a better understanding of customer preferences and find new opportunities, Generative AI models may be used for analyzing market research data.Product development: To identify new features and improvements, the use of neural AI models to analyze feedback from customers and product reviews is possible.Risk assessment: Generative AI models can be used to analyze financial and other data to assess risk.Challenges of using generative AI for sentiment analysisWhile generative AI has the potential to revolutionize sentiment analysis, there are also some challenges that need to be addressed:Requirements for data: Generative AI models require large amounts of training data to be effective. This can be a challenge for businesses and organizations that do not have access to large datasets.Model bias: Due to the biases inherent in the data they're trained on, generative AI models can be biased. This needs to be taken into account, as well as steps to mitigate it.Interpretation difficulties: The predictions of the generative AI models can be hard to understand. This makes it difficult to understand why a model made such an estimate, as well as to believe in its results.ConclusionThe potential for Generative AI to disrupt sentiment analysis is enormous. The generative AI models can achieve very high accuracy, scale to analyze a large volume of text, and be custom designed in order to meet the specific needs of different companies and organizations. In order to analyze sentiment for a broad range of text data, generative AI models can be used if you use fast engineering and quality tuning.A powerful new tool that can be applied to understand and analyze human language in new ways is sentiment analysis with generative artificial intelligence. As models of generative AI are improving, we can expect that this technology will be applied to a wider variety of applications and has an important impact on our daily lives and work.Author BioSangita Mahala is a passionate IT professional with an outstanding track record, having an impressive array of certifications, including 12x Microsoft, 11x GCP, 2x Oracle, and LinkedIn Marketing Insider Certified. She is a Google Crowdsource Influencer and IBM champion learner gold. She also possesses extensive experience as a technical content writer and accomplished book blogger. She is always Committed to staying with emerging trends and technologies in the IT sector.
Read more
  • 0
  • 0
  • 16355
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 €18.99/month. Cancel anytime
article-image-chatgpt-for-everyday-use
M.T. White
22 Aug 2023
14 min read
Save for later

ChatGPT for Everyday Use

M.T. White
22 Aug 2023
14 min read
IntroductionChatGPT is a revolutionary new technology that is making a large impact on society.  The full impact of ChatGPT cannot be fully known at the time of writing this article because of how novel the technology is.  However, what can be said is that since its introduction many industries have been trying to leverage it and increase productivity.  Simultaneously, everyday people are trying to learn to leverage it as well.  Overall, ChatGPT and similar systems are very new and the full impact of how to leverage them will take some more time to fully manifest.  This article is going to explore how ChatGPT can be used for everyday life by exploring a few use cases.What is ChatGPT?       Before we begin, it is important to understand what ChatGPT is and what it isn’t.  To begin ChatGPT is in a lay sense a super advanced chatbot.  More specifically, ChatGPT is known as a generative AI that uses Natural Language Processing (NLP) to create a dialog between a user and itself.  ChatGPT and similar systems are what are known as Large Language Models (LLMs).  In short, for AI models to work they have to be trained using data.  To train LLMs engineers use vast amounts such as books, articles, journals, and so on.  The result is a system like ChatGPT that has a vast knowledge base on many different subjects.  Before we can explore how to use ChatGPT for everyday life we need to explore how NOT to use ChatGPT. How not to use ChatGPT?ChatGPT is very powerful and can be used for many different things; however, is important to understand that ChatGPT is not a sage nor infallible.  Remember ChatGPT only knows about what it was trained on.  This means if the information it was taught was wrong or outdated so too will be the response it provides.  This means that as of writing this article ChatGPT cannot and should not be used as a replacement for humans.  Even with the answer ChatGPT gives, a decent level of domain knowledge is still required properly format and use its response.  As such, it is important to take suggestions with a certain amount of skepticism.  The best way to think of ChatGPT is as an extremely smart friend with a wide range of knowledge.  Though that friend may be very knowledgeable they can and will be wrong from time to time.  With that how does one use ChatGPT?How to use ChatGPT for everyday life?Using ChatGPT starts with what is called a prompt.  A prompt can be thought of as a query that the user feeds into ChatGPT.  This prompt is kind of like a Google search.  It will be a statement that asks ChatGPT a question or to perform a certain task.  Since ChatGPT used NLP, the prompt does not have to be formatted a certain way nor does it require a specific syntax.  In other words, a prompt can be entered the same way a user would pose the instruction to another person.  However, it is good to provide ChatGPT will enough information to adequately describe the problem.  An example, of a prompt is as follows:How can ChatGPT be used for everyday life?Once the prompt is run ChatGPT will produce text with suggestions, answers, programming software, or whatever was asked for.  That output that is produced is called a response which will have the answer to the request/question. There are a lot of ways ChatGPT can be used for everyday life.  In terms of professional life, ChatGPT can make an excellent assistant.  For most “text-based” jobs, that is jobs that don’t require design such as drawing diagrams, ChatGPT can be an excellent assistant.  For example, suppose a programmer needs a small piece of software for a project, assume they need a SQL query that can pull all the rows between two dates.  The user could use a prompt like the following:Write a SQL query that will pull all rows between January 3 and March 31.This prompt will produce the following response:To pull all rows between January 3 and March 31 from a table, you'll need to use the WHERE clause to filter the results based on the date column. Assuming you have a date column named "date_column" in your table, here's the SQL query: sqlCopy code SELECT * FROM your_table WHERE date_column >= '2023-01-03' AND date_column <= '2023-03-31'; Replace "your_table" with the actual name of your table, and "date_column" with the name of the column that contains the dates you want to filter. The query will select all rows where the "date_column" is on or after January 3, 2023, and on or before March 31, 2023.For those that are not familiar with SQL, the prompt produced a simple query with generic placeholders that will pull the correct data out of a database. Not everyone is a programmer, so how could a non-programmer use ChatGPT as an assistant?  One area in which ChatGPT can be used is by composing emails.  Suppose a salesperson needs to inform Tom, their boss, that a $1 million deal was closed today via email.  They could use a prompt like the following:Write Tom an email saying that the deal with Company X went through, and we made $1 million.The response would be something along the lines of:Subject: Successful Deal with Company X - $1 Million Revenue Achieved Hi Tom, I hope this email finds you well. I am thrilled to share some exciting news with you! The deal with Company X has successfully gone through, and I am delighted to inform you that we have made an astounding $1 million in revenue from this collaboration. This achievement wouldn't have been possible without the hard work and dedication of our entire team. It reflects the trust and confidence our partners have in our capabilities, and I believe it marks a significant milestone for our company's growth and success. I want to express my gratitude to you for your valuable contributions and expertise that played a crucial role in making this deal a reality. Your efforts have been commendable, and I am truly grateful for your commitment to the success of this venture. Let's take a moment to celebrate this triumph together. Your exceptional skills and commitment to excellence have made this accomplishment possible, and I am genuinely proud to have you as part of our team. Looking forward to even more prosperous ventures in the future. Once again, congratulations to you and the entire team! Best regards, [Your Name] [Your Title/Position] [Company Name] [Email] [Phone Number]In this case, all the users would have to do is proofread the email, copy it, and send it.  Now, for applications such as this proofreading is very important.  As stated before, ChatGPT is not a mind reader nor a sage, as such it may produce emails with wrong information, illogical wording, or anything else.  So, it’ll pay to read the message before sending it. Learning ToolA very powerful use of ChatGPT is as a learning tool.  For example, students can use this tool to help them master a new subject, generate practice problems, and even check answers to an extent.  Assume the user needs a few extra practice problems for their English homework, they could enter a prompt like the following:Generate some grammar practice problems for a 1st year English classWhich would yield the following output:Of course! Here are some grammar practice problems suitable for a 1st-year English class: 1. Subject-Verb Agreement: Choose the correct verb form that agrees with the subject in each sentence: a) The cat (purr/purrs) when it is happy. b) My friend and I (is/are) going to the movies tonight. c) Neither the students nor the teacher (understands/understand) the assignment. 2. Pronoun Usage: Select the appropriate pronoun to complete each sentence: a) She invited Sarah and (I/me) to the party. b) My sister and (her/she) are going shopping. c) The book is for (he/him) and his brother. 3. Articles (a/an/the): Insert the correct article (a, an, the, or no article) in each sentence: a) Can you pass me (a/an) apple, please? b) I need to buy (the) new shoes for the dance. c) My friend has (no article) car. 4. Prepositions: Choose the correct preposition to complete each sentence: a) We arrived (in/on/at) the airport fifteen minutes early. b) The cat is sleeping (under/below/above) the table. c) She's going to the store (by/with/for) her mom. 5. Verb Tenses: Identify the correct verb tense used in each sentence: a) They (will be/was) studying for the exam all night. b) By the time we arrived, the party (had started/has started). c) I (am/was) watching a movie right now. 6. Adjective vs. Adverb: Decide whether the word in parentheses should be an adjective or adverb in the sentence: a) She sings (beautiful/beautifully). b) The (happy/happily) children played in the park. c) The (quick/quickly) fox jumped over the fence. 7. Negation: Add the necessary negative word to each sentence: a) I have (no/not) seen him today. b) She does (not/none) like coffee. c) We will (not/none) be attending the party. 8. Passive Voice: Rewrite the following sentences in the passive voice: a) The chef cooked the meal. b) The teacher is explaining the lesson. c) The storm destroyed the old building. 9. Comparative and Superlative Forms: Complete each sentence with the correct comparative or superlative form of the adjective: a) This book is (interesting/more interesting/most interesting) than the last one. b) She is (tall/taller/tallest) than her sister. c) It was the (big/biggest/bigger) cake at the party. 10. Reported Speech: Rewrite the following sentences using reported speech: a) Mary said, "I am going to the store." b) "I will call you later," said John. c) "They have arrived," she said. These grammar practice problems aim to reinforce essential grammar rules and concepts, helping 1st-year English students develop their language skills and proficiency.As can be seen, ChatGPT generated a number of questions that one would usually find in an English 101 class.  Now, if the user wanted a more specific answer that could reword the prompt to accommodate. ChatGPT is also a great teaching tool as well.  Teachers can also use this tool to generate lesson plans, write intros for learning modules, generate problems, and more.  Overall, ChatGPT can be used as an excellent tool for academia in general.Now, it is important to remember that ChatGPT is not all-knowing.  It can only generate responses based on the data that it was trained on.  This means if the user is asking questions about a subject where there is little or no publicly available data the response probably isn’t going to be of the highest quality.  For example, if someone askedHow do I build a working time machine?They’re going to get the following:Building a working time machine, as depicted in science fiction, is currently beyond our technological capabilities and goes against the known laws of physics. Time travel, as popularly imagined, involves traveling backward or forward in time, similar to how we move through space. As of my last update in September 2021, time travel remains a concept explored mainly in theoretical physics, and no practical time machine has been built or proven to be possible. Several theories, such as the theory of relativity, suggest that time travel could be theoretically feasible under certain conditions, but these ideas are still purely theoretical and have not been put into practice. It's important to remember that building a time machine would present enormous scientific, technological, and ethical challenges. It could raise paradoxes, such as the "grandfather paradox," where a time traveler could potentially alter past events and create inconsistencies in history. As of now, time travel remains a fascinating topic in science fiction and theoretical physics, but it is not something that can be practically accomplished with our current understanding and technology. The scientific community continues to explore the nature of time and the universe, but building a working time machine as portrayed in movies and books is not yet a reality.ChatGPT gave some general information on time travel but unfortunately, it didn’t generate any schematics.  Again, this goes back to ChatGPT not being a sage as it only knows what it was “taught”.  Since there is no data on how to build a working time machine ChatGPT could not fully answer the prompt. Now, If one looks at the English question prompt above, one will notice that practice questions were quite broad.  It is common to have to “dig” with ChatGPT.  In other words, a person may have to refine the queriers to get what they need.  To get some practice try re-wording the prompt to generate practice questions for only passive writing.  SummaryChatGPT is a tool, and like any other tool, what it’s used for is up to the user.  As was seen in this article, ChatGPT is an excellent tool for helping a person through their day by generating software, emails, and so on.  ChatGPT can also be used as a great learning or teaching device to help students and teachers generate practice problems, create lesson plans, and so much more.  However, as was stated so many numerous times.  Unless ChatGPT has been trained on something it does not know about it.  This means that asking it things like how to build a time machine or domain specific concepts aren’t going to return quality responses.  Also, even if ChatGPT has been trained on the prompt, it may not always generate a quality response.  No matter the use case, the response should be vetted for accuracy.  This may mean doing a little extra research with the response given, testing the output, or whatever needs to be done to verify the response. Overall, ChatGPT at the time of writing this article is less than a year old.  This means that the full implication of using ChatGPT are not fully understood.  Also, how to fully leverage ChatGPT is not understood yet either.  What can be said is that ChatGPT and similar LLM systems will probably be the next Google.  In terms of everyday use, the only true inhibitors are the user's imagination and the data that was used to train ChatGPT.Author BioM.T. White has been programming since the age of 12. His fascination with robotics flourished when he was a child programming microcontrollers such as Arduino. M.T. currently holds an undergraduate degree in mathematics, and a master's degree in software engineering, and is currently working on an MBA in IT project management. M.T. is currently working as a software developer for a major US defense contractor and is an adjunct CIS instructor at ECPI University. His background mostly stems from the automation industry where he programmed PLCs and HMIs for many different types of applications. M.T. has programmed many different brands of PLCs over the years and has developed HMIs using many different tools.Author of the book: Mastering PLC Programming
Read more
  • 0
  • 0
  • 16351

article-image-creating-jee-application-ejb
Packt
24 Sep 2015
11 min read
Save for later

Creating a JEE Application with EJB

Packt
24 Sep 2015
11 min read
In this article by Ram Kulkarni, author of Java EE Development with Eclipse (e2), we will be using EJBs (Enterprise Java Beans) to implement business logic. This is ideal in scenarios where you want components that process business logic to be distributed across different servers. But that is just one of the advantages of EJB. Even if you use EJBs on the same server as the web application, you may gain from a number of services that the EJB container provides to the applications through EJBs. You can specify security constraints for calling EJB methods declaratively (using annotations), and you can also easily specify transaction boundaries (specify which method calls from a part of one transaction) using annotations. In addition to this, the container handles the life cycle of EJBs, including pooling of certain types of EJB objects so that more objects can be created when the load on the application increases. (For more resources related to this topic, see here.) In this article, we will create the same application using EJBs and deploy it in a Glassfish 4 server. But before that, you need to understand some basic concepts of EJBs. Types of EJB EJBs can be of following types as per the EJB 3 specifications: Session bean: Stateful session bean Stateless session bean Singleton session bean Message-driven bean In this article, we will focus on session beans. Session beans In general, session beans are meant for containing methods used to execute the main business logic of enterprise applications. Any Plain Old Java Object (POJO) can be annotated with the appropriate EJB-3-specific annotations to make it a session bean. Session beans come in three types, as follows. Stateful session bean One stateful session bean serves requests for one client only. There is a one-to-one mapping between the stateful session bean and the client. Therefore, stateful beans can hold state data for the client between multiple method calls. In our CourseManagement application, we can use a stateful bean to hold the Student data (student profile and the courses taken by him/her) after a student logs-in. The state maintained by the Stateful bean is lost when the server restarts or when the session times out. Since there is one stateful bean per client, using a stateful bean might impact the scalability of the application. We use the @Stateful annotation to create a stateful session bean. Stateless session bean A stateless session bean does not hold any state information for any client. Therefore, one session bean can be shared across multiple clients. The EJB container maintains pools of stateless beans, and when a client request comes, it takes out a bean from the pool, executes methods, and returns the bean to the pool. Stateless session beans provide excellent scalability because they can be shared and need not be created for each client. We use the @Stateless annotation to create a stateless session bean. Singleton session bean As the name suggests, there is only one instance of a singleton bean class in the EJB container (this is true in the clustered environment too; each EJB container will have an instance of a singleton bean). This means that they are shared by multiple clients, and they are not pooled by EJB containers (because there can be only one instance). Since a singleton session bean is a shared resource, we need to manage concurrency in it. Java EE provides two concurrency management options for singleton session beans: container-managed concurrency and bean-managed concurrency. Container-managed concurrency can easily be specified by annotations. See https://docs.oracle.com/javaee/7/tutorial/ejb-basicexamples002.htm#GIPSZ for more information on managing concurrency in a singleton session bean. Using a singleton bean could have an impact on the scalability of the application if there are resource contentions in the code. We use the @Singleton annotation to create a singleton session bean Accessing a session bean from the client Session beans can be designed to be accessed locally (within the same application as a session bean) or remotely (from a client running in a different application or JVM) or both. In the case of remote access, session beans are required to implement a remote interface. For local access, session beans can implement a local interface or no interface (the no-interface view of a session bean). Remote and local interfaces that session beans implement are sometimes also called business interfaces, because they typically expose the primary business functionality. Creating a no-interface session bean To create a session bean with a no-interface view, create a POJO and annotate it with the appropriate EJB annotation type and @LocalBean. For example, we can create a local stateful Student bean as follows: import javax.ejb.LocalBean; import javax.ejb.Singleton; @Singleton @LocalBean public class Student { ... } Accessing a session bean using dependency injection You can access session beans by either using the @EJBannotation (for dependency injection) or performing a Java Naming and Directory Interface (JNDI) lookup. EJB containers are required to make the JNDI URLs of EJBs available to clients. Dependency injection of session beans using @EJB work only for managed components, that is, components of the application whose life cycle is managed by the EJB container. When a component is managed by the container, it is created (instantiated) by the container and also destroyed by the container. You do not create managed components using the new operator. JEE-managed components that support direct injection of EJBs are servlets, managed beans of JSF pages and EJBs themselves (one EJB can have other EJBs injected into it). Unfortunately, you cannot have a web container injecting EJBs into JSPs or JSP beans. Also, you cannot have EJBs injected into any custom classes that you create and are instantiated using the new operator. We can use the Student bean (created previously) from a managed bean of JSF, as follows: import javax.ejb.EJB; import javax.faces.bean.ManagedBean; @ManagedBean public class StudentJSFBean { @EJB private Student studentEJB; } Note that if you create an EJB with a no-interface view, then all the public methods in that EJB will be exposed to the clients. If you want to control which methods can be called by clients, then you should implement the business interface. Creating a session bean using a local business interface A business interface for EJB is a simple Java interface with either the @Remote or @Local annotation. So we can create a local interface for the Student bean as follows: import java.util.List; import javax.ejb.Local; @Local public interface StudentLocal { public List<Course> getCourses(); } We implement a session bean like this: import java.util.List; import javax.ejb.Local; import javax.ejb.Stateful; @Stateful @Local public class Student implements StudentLocal { @Override public List<CourseDTO> getCourses() { //get courses are return … } } Clients can access the Student EJB only through the local interface: import javax.ejb.EJB; import javax.faces.bean.ManagedBean; @ManagedBean public class StudentJSFBean { @EJB private StudentLocal student; } The session bean can implement multiple business interfaces. Accessing a session bean using a JNDI lookup Though accessing EJB using dependency injection is the easiest way, it works only if the container manages the class that accesses the EJB. If you want to access EJB from a POJO that is not a managed bean, then dependency injection will not work. Another scenario where dependency injection does not work is when EJB is deployed in a separate JVM (this could be on a remote server). In such cases, you will have to access EJB using a JNDI lookup (visit https://docs.oracle.com/javase/tutorial/jndi/ for more information on JNDI). JEE applications can be packaged in an Enterprise Application Archive (EAR), which contains a .jar file for EJBs and a WAR file for web applications (and the lib folder contains the libraries required for both). If, for example, the name of an EAR file is CourseManagement.ear and the name of an EJB JAR file in it is CourseManagementEJBs.jar, then the name of the application is CourseManagement (the name of the EAR file) and the module name is CourseManagementEJBs. The EJB container uses these names to create a JNDI URL for lookup EJBs. A global JNDI URL for EJB is created as follows: "java:global/<application_name>/<module_name>/<bean_name>![<bean_interface>]" java:global: Indicates that it is a global JNDI URL. <application_name>: The application name is typically the name of the EAR file. <module_name>: This is the name of the EJB JAR. <bean_name>: This is the name of the EJB bean class. <bean_interface>: This is optional if EJB has a no-interface view, or if it implements only one business interface. Otherwise, it is a fully qualified name of a business interface. EJB containers are also required to publish two more variations of JNDI URLs for each EJB. These are not global URLs, which means that they can't be used to access EJBs from clients that are not in the same JEE application (in the same EAR): "java:app/[<module_name>]/<bean_name>![<bean_interface>]" "java:module/<bean_name>![<bean_interface>]" The first URL can be used if the EJB client is in the same application, and the second URL can be used if the client is in the same module (the same JAR file as the EJB). Before you look up any URL in a JNDI server, you need to create an InitialContext that includes information, among other things such as the hostname of JNDI server and the port on which it is running. If you are creating InitialContext in the same server, then there is no need to specify these attributes: InitialContext initCtx = new InitialContext(); Object obj = initCtx.lookup("jndi_url"); We can use the following JNDI URLs to access a no-interface (LocalBean) Student EJB (assuming that the name of the EAR file is CourseManagement and the name of the JAR file for EJBs is CourseManagementEJBs): URL When to use java:global/CourseManagement/ CourseManagementEJBs/Student The client can be anywhere in the EAR file, because we are using a global URL. Note that we haven't specified the interface name because we are assuming that the Student bean provides a no-interface view in this example. java:app/CourseManagementEJBs/Student The client can be anywhere in the EAR. We skipped the application name because the client is expected to be in the same application. This is because the namespace of the URL is java:app. java:module/Student The client must be in the same JAR file as EJB. We can use the following JNDI URLs to access the Student EJB that implemented a local interface, StudentLocal: URL When to use java:global/CourseManagement/ CourseManagementEJBs/Student!packt.jee.book.ch6.StudentLocal The client can be anywhere in the EAR file, because we are using a global URL. java:global/CourseManagement/ CourseManagementEJBs/Student The client can be anywhere in the EAR. We skipped the interface name because the bean implements only one business interface. Note that the object returned from this call will be of the StudentLocal type, and not Student. java:app/CourseManagementEJBs/Student Or java:app/CourseManagementEJBs/Student!packt.jee.book.ch6.StudentLocal   The client can be anywhere in the EAR. We skipped the application name because the JNDI namespace is java:app. java:module/Student Or java:module/Student!packt.jee.book.ch6.StudentLocal The client must be in the same EAR as the EJB. Here is an example of how we can call the Student bean with the local business interface from one of the objects (that is not managed by the web container) in our web application: InitialContext ctx = new InitialContext(); StudentLocal student = (StudentLocal) ctx.loopup ("java:app/CourseManagementEJBs/Student"); return student.getCourses(id) ; //get courses from Student EJB Creating EAR for Deployment outside Eclipse. Summary EJBs are ideal for writing business logic in web applications. They can act as the perfect bridge between web interface components, such as a JSF, servlet, or JSP, and data access objects, such as JDO. EJBs can be distributed across multiple JEE application servers (this could improve application scalability) and their life cycle is managed by the container. EJBs can easily be injected into managed objects or can be looked up using JNDI. The Eclipse JEE makes creating and consuming EJBs very easy. The JEE application server Glassfish can also be managed and applications can be deployed from within Eclipse. Resources for Article: Further resources on this subject: Contexts and Dependency Injection in NetBeans[article] WebSockets in Wildfly[article] Creating Java EE Applications [article]
Read more
  • 0
  • 0
  • 16346

article-image-understanding-php-basics
Packt
17 Feb 2016
27 min read
Save for later

Understanding PHP basics

Packt
17 Feb 2016
27 min read
In this article by Antonio Lopez Zapata, the author of the book Learning PHP 7, you need to understand not only the syntax of the language, but also its grammatical rules, that is, when and why to use each element of the language. Luckily, for you, some languages come from the same root. For example, Spanish and French are romance languages as they both evolved from spoken Latin; this means that these two languages share a lot of rules, and learning Spanish if you already know French is much easier. (For more resources related to this topic, see here.) Programming languages are quite the same. If you already know another programming language, it will be very easy for you to go through this chapter. If it is your first time though, you will need to understand from scratch all the grammatical rules, so it might take some more time. But fear not! We are here to help you in this endeavor. In this chapter, you will learn about these topics: PHP in web applications Control structures Functions PHP in web applications Even though the main purpose of this chapter is to show you the basics of PHP, doing so in a reference-manual way is not interesting enough. If we were to copy paste what the official documentation says, you might as well go there and read it by yourself. Instead, let's not forget the main purpose of this book and your main goal—to write web applications with PHP. We will show you how can you apply everything you are learning as soon as possible, before you get too bored. In order to do that, we will go through the journey of building an online bookstore. At the very beginning, you might not see the usefulness of it, but that is just because we still haven't seen all that PHP can do. Getting information from the user Let's start by building a home page. In this page, we are going to figure out whether the user is looking for a book or just browsing. How do we find this out? The easiest way right now is to inspect the URL that the user used to access our application and extract some information from there. Save this content as your index.php file: <?php $looking = isset($_GET['title']) || isset($_GET['author']); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bookstore</title> </head> <body> <p>You lookin'? <?php echo (int) $looking; ?></p> <p>The book you are looking for is</p> <ul> <li><b>Title</b>: <?php echo $_GET['title']; ?></li> <li><b>Author</b>: <?php echo $_GET['author']; ?></li> </ul> </body> </html> And now, access http://localhost:8000/?author=Harper Lee&title=To Kill a Mockingbird. You will see that the page is printing some of the information that you passed on to the URL. For each request, PHP stores in an array—called $_GET- all the parameters that are coming from the query string. Each key of the array is the name of the parameter, and its associated value is the value of the parameter. So, $_GET contains two entries: $_GET['author'] contains Harper Lee and $_GET['title'] contains To Kill a Mockingbird. On the first highlighted line, we are assigning a Boolean value to the $looking variable. If either $_GET['title'] or $_GET['author'] exists, this variable will be true; otherwise, false. Just after that, we close the PHP tag and then we start printing some HTML, but as you can see, we are actually mixing HTML with PHP code. Another interesting line here is the second highlighted line. We are printing the content of $looking, but before that, we cast the value. Casting means forcing PHP to transform a type of value to another one. Casting a Boolean to an integer means that the resultant value will be 1 if the Boolean is true or 0 if the Boolean is false. As $looking is true since $_GET contains valid keys, the page shows 1. If we try to access the same page without sending any information as in http://localhost:8000, the browser will say "Are you looking for a book? 0". Depending on the settings of your PHP configuration, you will see two notice messages complaining that you are trying to access the keys of the array that do not exist. Casting versus type juggling We already knew that when PHP needs a specific type of variable, it will try to transform it, which is called type juggling. But PHP is quite flexible, so sometimes, you have to be the one specifying the type that you need. When printing something with echo, PHP tries to transform everything it gets into strings. Since the string version of the false Boolean is an empty string, this would not be useful for our application. Casting the Boolean to an integer first assures that we will see a value, even if it is just "0". HTML forms HTML forms are one of the most popular ways to collect information from users. They consist a series of fields called inputs in the HTML world and a final submit button. In HTML, the form tag contains two attributes: the action points, where the form will be submitted and method that specifies which HTTP method the form will use—GET or POST. Let's see how it works. Save the following content as login.html and go to http://localhost:8000/login.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bookstore - Login</title> </head> <body> <p>Enter your details to login:</p> <form action="authenticate.php" method="post"> <label>Username</label> <input type="text" name="username" /> <label>Password</label> <input type="password" name="password" /> <input type="submit" value="Login"/> </form> </body> </html> This form contains two fields, one for the username and one for the password. You can see that they are identified by the name attribute. If you try to submit this form, the browser will show you a Page Not Found message, as it is trying to access http://localhost:8000/authenticate.phpand the web server cannot find it. Let's create it then: <?php $submitted = !empty($_POST); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bookstore</title> </head> <body> <p>Form submitted? <?php echo (int) $submitted; ?></p> <p>Your login info is</p> <ul> <li><b>username</b>: <?php echo $_POST['username']; ?></li> <li><b>password</b>: <?php echo $_POST['password']; ?></li> </ul> </body> </html> As with $_GET, $_POST is an array that contains the parameters received by POST. In this piece of code, we are first asking whether that array is not empty—note the ! operator. Afterwards, we just display the information received, just as in index.php. Note that the keys of the $_POST array are the values for the name argument of each input field. Control structures So far, our files have been executed line by line. Due to that, we are getting some notices on some scenarios, such as when the array does not contain what we are looking for. Would it not be nice if we could choose which lines to execute? Control structures to the rescue! A control structure is like a traffic diversion sign. It directs the execution flow depending on some predefined conditions. There are different control structures, but we can categorize them in conditionals and loops. A conditional allows us to choose whether to execute a statement or not. A loop will execute a statement as many times as you need. Let's take a look at each one of them. Conditionals A conditional evaluates a Boolean expression, that is, something that returns a value. If the expression is true, it will execute everything inside its block of code. A block of code is a group of statements enclosed by {}. Let's see how it works: <?php echo "Before the conditional."; if (4 > 3) { echo "Inside the conditional."; } if (3 > 4) { echo "This will not be printed."; } echo "After the conditional."; In this piece of code, we are using two conditionals. A conditional is defined by the keyword if followed by a Boolean expression in parentheses and by a block of code. If the expression is true, it will execute the block; otherwise, it will skip it. You can increase the power of conditionals by adding the keyword else. This tells PHP to execute a block of code if the previous conditions were not satisfied. Let's see an example: if (2 > 3) { echo "Inside the conditional."; } else { echo "Inside the else."; } This will execute the code inside else as the condition of if was not satisfied. Finally, you can also add an elseif keyword followed by another condition and block of code to continue asking PHP for more conditions. You can add as many elseif as you need after if. If you add else, it has to be the last one of the chain of conditions. Also keep in mind that as soon as PHP finds a condition that resolves to true, it will stop evaluating the rest of the conditions: <?php if (4 > 5) { echo "Not printed"; } elseif (4 > 4) { echo "Not printed"; } elseif (4 == 4) { echo "Printed."; } elseif (4 > 2) { echo "Not evaluated."; } else { echo "Not evaluated."; } if (4 == 4) { echo "Printed"; } In this last example, the first condition that evaluates to true is the one that is highlighted. After that, PHP does not evaluate any more conditions until a new if starts. With this knowledge, let's try to clean up a bit of our application, executing statements only when needed. Copy this code to your index.php file: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bookstore</title> </head> <body> <p> <?php if (isset($_COOKIE[username'])) { echo "You are " . $_COOKIE['username']; } else { echo "You are not authenticated."; } ?> </p> <?php if (isset($_GET['title']) && isset($_GET['author'])) { ?> <p>The book you are looking for is</p> <ul> <li><b>Title</b>: <?php echo $_GET['title']; ?></li> <li><b>Author</b>: <?php echo $_GET['author']; ?></li> </ul> <?php } else { ?> <p>You are not looking for a book?</p> <?php } ?> </body> </html> In this new code, we are mixing conditionals and HTML code in two different ways. The first one opens a PHP tag and adds an if-else clause that will print whether we are authenticated or not with echo. No HTML is merged within the conditionals, which makes it clear. The second option—the second highlighted block—shows an uglier solution, but this is sometimes necessary. When you have to print a lot of HTML code, echo is not that handy, and it is better to close the PHP tag; print all the HTML you need and then open the tag again. You can do that even inside the code block of an if clause, as you can see in the code. Mixing PHP and HTML If you feel like the last file we edited looks rather ugly, you are right. Mixing PHP and HTML is confusing, and you have to avoid it by all means. Let's edit our authenticate.php file too, as it is trying to access $_POST entries that might not be there. The new content of the file would be as follows: <?php $submitted = isset($_POST['username']) && isset($_POST['password']); if ($submitted) { setcookie('username', $_POST['username']); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bookstore</title> </head> <body> <?php if ($submitted): ?> <p>Your login info is</p> <ul> <li><b>username</b>: <?php echo $_POST['username']; ?></li> <li><b>password</b>: <?php echo $_POST['password']; ?></li> </ul> <?php else: ?> <p>You did not submitted anything.</p> <?php endif; ?> </body> </html> This code also contains conditionals, which we already know. We are setting a variable to know whether we've submitted a login or not and to set the cookies if we have. However, the highlighted lines show you a new way of including conditionals with HTML. This way, tries to be more readable when working with HTML code, avoiding the use of {} and instead using : and endif. Both syntaxes are correct, and you should use the one that you consider more readable in each case. Switch-case Another control structure similar to if-else is switch-case. This structure evaluates only one expression and executes the block depending on its value. Let's see an example: <?php switch ($title) { case 'Harry Potter': echo "Nice story, a bit too long."; break; case 'Lord of the Rings': echo "A classic!"; break; default: echo "Dunno that one."; break; } The switch case takes an expression; in this case, a variable. It then defines a series of cases. When the case matches the current value of the expression, PHP executes the code inside it. As soon as PHP finds break, it will exit switch-case. In case none of the cases are suitable for the expression, if there is a default case         , PHP will execute it, but this is optional. You also need to know that breaks are mandatory if you want to exit switch-case. If you do not specify any, PHP will keep on executing statements, even if it encounters a new case. Let's see a similar example but without breaks: <?php $title = 'Twilight'; switch ($title) { case 'Harry Potter': echo "Nice story, a bit too long."; case 'Twilight': echo 'Uh...'; case 'Lord of the Rings': echo "A classic!"; default: echo "Dunno that one."; } If you test this code in your browser, you will see that it is printing "Uh...A classic!Dunno that one.". PHP found that the second case is valid so it executes its content. But as there are no breaks, it keeps on executing until the end. This might be the desired behavior sometimes, but not usually, so we need to be careful when using it! Loops Loops are control structures that allow you to execute certain statements several times—as many times as you need. You might use them on several different scenarios, but the most common one is when interacting with arrays. For example, imagine you have an array with elements but you do not know what is in it. You want to print all its elements so you loop through all of them. There are four types of loops. Each of them has their own use cases, but in general, you can transform one type of loop into another. Let's see them closely While While is the simplest of the loops. It executes a block of code until the expression to evaluate returns false. Let's see one example: <?php $i = 1; while ($i < 4) { echo $i . " "; $i++; } Here, we are defining a variable with the value 1. Then, we have a while clause in which the expression to evaluate is $i < 4. This loop will execute the content of the block of code until that expression is false. As you can see, inside the loop we are incrementing the value of $i by 1 each time, so after 4 iterations, the loop will end. Check out the output of that script, and you will see "0 1 2 3". The last value printed is 3, so by that time, $i was 3. After that, we increased its value to 4, so when the while was evaluating whether $i < 4, the result was false. Whiles and infinite loops One of the most common problems with while loops is creating an infinite loop. If you do not add any code inside while, which updates any of the variables considered in the while expression so it can be false at some point, PHP will never exit the loop! For This is the most complex of the four loops. For defines an initialization expression, an exit condition, and the end of the iteration expression. When PHP first encounters the loop, it executes what is defined as the initialization expression. Then, it evaluates the exit condition, and if it resolves to true, it enters the loop. After executing everything inside the loop, it executes the end of the iteration expression. Once this is done, it will evaluate the end condition again, going through the loop code and the end of iteration expression until it evaluates to false. As always, an example will help clarify this: <?php for ($i = 1; $i < 10; $i++) { echo $i . " "; } The initialization expression is $i = 1 and is executed only the first time. The exit condition is $i < 10, and it is evaluated at the beginning of each iteration. The end of the iteration expression is $i++, which is executed at the end of each iteration. This example prints numbers from 1 to 9. Another more common usage of the for loop is with arrays: <?php $names = ['Harry', 'Ron', 'Hermione']; for ($i = 0; $i < count($names); $i++) { echo $names[$i] . " "; } In this example, we have an array of names. As it is defined as a list, its keys will be 0, 1, and 2. The loop initializes the $i variable to 0, and it will iterate until the value of $i is not less than the amount of elements in the array 3 The first iteration $i is 0, the second will be 1, and the third one will be 2. When $i is 3, it will not enter the loop as the exit condition evaluates to false. On each iteration, we are printing the content of the $i position of the array; hence, the result of this code will be all three names in the array. We careful with exit conditions It is very common to set an exit condition. This is not exactly what we need, especially with arrays. Remember that arrays start with 0 if they are a list, so an array of 3 elements will have entries 0, 1, and 2. Defining the exit condition as $i <= count($array) will cause an error on your code, as when $i is 3, it also satisfies the exit condition and will try to access to the key 3, which does not exist. Foreach The last, but not least, type of loop is foreach. This loop is exclusive for arrays, and it allows you to iterate an array entirely, even if you do not know its keys. There are two options for the syntax, as you can see in these examples: <?php $names = ['Harry', 'Ron', 'Hermione']; foreach ($names as $name) { echo $name . " "; } foreach ($names as $key => $name) { echo $key . " -> " . $name . " "; } The foreach loop accepts an array; in this case, $names. It specifies a variable, which will contain the value of the entry of the array. You can see that we do not need to specify any end condition, as PHP will know when the array has been iterated. Optionally, you can specify a variable that will contain the key of each iteration, as in the second loop. Foreach loops are also useful with maps, where the keys are not necessarily numeric. The order in which PHP will iterate the array will be the same order in which you used to insert the content in the array. Let's use some loops in our application. We want to show the available books in our home page. We have the list of books in an array, so we will have to iterate all of them with a foreach loop, printing some information from each one. Append the following code to the body tag in index.php: <?php endif; $books = [ [ 'title' => 'To Kill A Mockingbird', 'author' => 'Harper Lee', 'available' => true, 'pages' => 336, 'isbn' => 9780061120084 ], [ 'title' => '1984', 'author' => 'George Orwell', 'available' => true, 'pages' => 267, 'isbn' => 9780547249643 ], [ 'title' => 'One Hundred Years Of Solitude', 'author' => 'Gabriel Garcia Marquez', 'available' => false, 'pages' => 457, 'isbn' => 9785267006323 ], ]; ?> <ul> <?php foreach ($books as $book): ?> <li> <i><?php echo $book['title']; ?></i> - <?php echo $book['author']; ?> <?php if (!$book['available']): ?> <b>Not available</b> <?php endif; ?> </li> <?php endforeach; ?> </ul> The highlighted code shows a foreach loop using the : notation, which is better when mixing it with HTML. It iterates all the $books arrays, and for each book, it will print some information as a HTML list. Also note that we have a conditional inside a loop, which is perfectly fine. Of course, this conditional will be executed for each entry in the array, so you should keep the block of code of your loops as simple as possible. Functions A function is a reusable block of code that, given an input, performs some actions and optionally returns a result. You already know several predefined functions, such as empty, in_array, or var_dump. These functions come with PHP so you do not have to reinvent the wheel, but you can create your own very easily. You can define functions when you identify portions of your application that have to be executed several times or just to encapsulate some functionality. Function declaration Declaring a function means to write it down so that it can be used later. A function has a name, takes arguments, and has a block of code. Optionally, it can define what kind of value is returning. The name of the function has to follow the same rules as variable names; that is, it has to start by a letter or underscore and can contain any letter, number, or underscore. It cannot be a reserved word. Let's see a simple example: function addNumbers($a, $b) { $sum = $a + $b; return $sum; } $result = addNumbers(2, 3); Here, the function's name is addNumbers, and it takes two arguments: $a and $b. The block of code defines a new variable $sum that is the sum of both the arguments and then returns its content with return. In order to use this function, you just need to call it by its name, sending all the required arguments, as shown in the highlighted line. PHP does not support overloaded functions. Overloading refers to the ability of declaring two or more functions with the same name but different arguments. As you can see, you can declare the arguments without knowing what their types are, so PHP would not be able to decide which function to use. Another important thing to note is the variable scope. We are declaring a $sum variable inside the block of code, so once the function ends, the variable will not be accessible any more. This means that the scope of variables declared inside the function is just the function itself. Furthermore, if you had a $sum variable declared outside the function, it would not be affected at all since the function cannot access that variable unless we send it as an argument. Function arguments A function gets information from outside via arguments. You can define any number of arguments—including 0. These arguments need at least a name so that they can be used inside the function, and there cannot be two arguments with the same name. When invoking the function, you need to send the arguments in the same order as we declared them. A function may contain optional arguments; that is, you are not forced to provide a value for those arguments. When declaring the function, you need to provide a default value for those arguments, so in case the user does not provide a value, the function will use the default one: function addNumbers($a, $b, $printResult = false) { $sum = $a + $b; if ($printResult) { echo 'The result is ' . $sum; } return $sum; } $sum1 = addNumbers(1, 2); $sum1 = addNumbers(3, 4, false); $sum1 = addNumbers(5, 6, true); // it will print the result This new function takes two mandatory arguments and an optional one. The default value is false, and is used as a normal value inside the function. The function will print the result of the sum if the user provides true as the third argument, which happens only the third time that the function is invoked. For the first two times, $printResult is set to false. The arguments that the function receives are just copies of the values that the user provided. This means that if you modify these arguments inside the function, it will not affect the original values. This feature is known as sending arguments by a value. Let's see an example: function modify($a) { $a = 3; } $a = 2; modify($a); var_dump($a); // prints 2 We are declaring the $a variable with the value 2, and then we are calling the modify method, sending $a. The modify method modifies the $a argument, setting its value to 3. However, this does not affect the original value of $a, which reminds to 2 as you can see in the var_dump function. If what you want is to actually change the value of the original variable used in the invocation, you need to pass the argument by reference. To do that, you add & in front of the argument when declaring the function: function modify(&$a) { $a = 3; } Now, after invoking the modify function, $a will be always 3. Arguments by value versus by reference PHP allows you to do it, and in fact, some native functions of PHP use arguments by reference—remember the array sorting functions; they did not return the sorted array; instead, they sorted the array provided. But using arguments by reference is a way of confusing developers. Usually, when someone uses a function, they expect a result, and they do not want their provided arguments to be modified. So, try to avoid it; people will be grateful! The return statement You can have as many return statements as you want inside your function, but PHP will exit the function as soon as it finds one. This means that if you have two consecutive return statements, the second one will never be executed. Still, having multiple return statements can be useful if they are inside conditionals. Add this function inside your functions.php file: function loginMessage() { if (isset($_COOKIE['username'])) { return "You are " . $_COOKIE['username']; } else { return "You are not authenticated."; } } Let's use it in your index.php file by replacing the highlighted content—note that to save some tees, I replaced most of the code that was not changed at all with //…: //... <body> <p><?php echo loginMessage(); ?></p> <?php if (isset($_GET['title']) && isset($_GET['author'])): ?> //... Additionally, you can omit the return statement if you do not want the function to return anything. In this case, the function will end once it reaches the end of the block of code. Type hinting and return types With the release of PHP7, the language allows developers to be more specific about what functions get and return. You can—always optionally—specify the type of argument that the function needs, for example, type hinting, and the type of result the function will return—return type. Let's first see an example: <?php declare(strict_types=1); function addNumbers(int $a, int $b, bool $printSum): int { $sum = $a + $b; if ($printSum) { echo 'The sum is ' . $sum; } return $sum; } addNumbers(1, 2, true); addNumbers(1, '2', true); // it fails when strict_types is 1 addNumbers(1, 'something', true); // it always fails This function states that the arguments need to be an integer, and Boolean, and that the result will be an integer. Now, you know that PHP has type juggling, so it can usually transform a value of one type to its equivalent value of another type, for example, the string 2 can be used as integer 2. To stop PHP from using type juggling with the arguments and results of functions, you can declare the strict_types directive as shown in the first highlighted line. This directive has to be declared on the top of each file, where you want to enforce this behavior. The three invocations work as follows: The first invocation sends two integers and a Boolean, which is what the function expects. So, regardless of the value of strict_types, it will always work. The second invocation sends an integer, a string, and a Boolean. The string has a valid integer value, so if PHP was allowed to use type juggling, the invocation would resolve to just normal. But in this example, it will fail because of the declaration on top of the file. The third invocation will always fail as the something string cannot be transformed into a valid integer. Let's try to use a function within our project. In our index.php file, we have a foreach loop that iterates the books and prints them. The code inside the loop is kind of hard to understand as it is mixing HTML with PHP, and there is a conditional too. Let's try to abstract the logic inside the loop into a function. First, create the new functions.php file with the following content: <?php function printableTitle(array $book): string { $result = '<i>' . $book['title'] . '</i> - ' . $book['author']; if (!$book['available']) { $result .= ' <b>Not available</b>'; } return $result; } This file will contain our functions. The first one, printableTitle, takes an array representing a book and builds a string with a nice representation of the book in HTML. The code is the same as before, just encapsulated in a function. Now, index.php will have to include the functions.php file and then use the function inside the loop. Let's see how this is done: <?php require_once 'functions.php' ?> <!DOCTYPE html> <html lang="en"> //... ?> <ul> <?php foreach ($books as $book): ?> <li><?php echo printableTitle($book); ?> </li> <?php endforeach; ?> </ul> //... Well, now our loop looks way cleaner, right? Also, if we need to print the title of the book somewhere else, we can reuse the function instead of duplicating code! Summary In this article, we went through all the basics of procedural PHP while writing simple examples in order to practice them. You now know how to use variables and arrays with control structures and functions and how to get information from HTTP requests among others. Resources for Article: Further resources on this subject: Getting started with Modernizr using PHP IDE[article] PHP 5 Social Networking: Implementing Public Messages[article] Working with JSON in PHP jQuery[article]
Read more
  • 0
  • 0
  • 16342

article-image-customizing-kernel-and-boot-sequence
Packt
08 Nov 2016
35 min read
Save for later

Customizing Kernel and Boot Sequence

Packt
08 Nov 2016
35 min read
In this article by Ivan Morgillo and Stefano Viola, the authors of the book Learning Embedded Android N Programming, you will learn about the kernel customization to the boot sequence. You will learn how to retrieve the proper source code for Google devices, how to set up the build environment, how to build your first custom (For more resources related to this topic, see here.) version of the Linux kernel, and deploy it to your device. You will learn about: Toolchain overview How to configure the host system to compile your own Linux kernel How to configure the Linux kernel Linux kernel overview Android boot sequence The Init process An overview of the Linux kernel We learned how Android has been designed and built around the Linux kernel. One of the reasons to choose the Linux kernel was its unquestioned flexibility and the infinite possibilities to adjust it to any specific scenario and requirement. These are the features that have made Linux the most popular kernel in the embedded industry. Linux kernel comes with a GPL license. This particular license allowed Google to contribute to the project since the early stages of Android. Google provided bug fixing and new features, helping Linux to overcome a few obstacles and limitations of the 2.6 version. In the beginning, Linux 2.6.32 was the most popular version for the most part of the Android device market. Nowadays, we see more and more devices shipping with the new 3.x versions. The following screenshot shows the current build for the official Google Motorola Nexus 6, with kernel 3.10.40: The Android version we created in the previouly was equipped with a binary version of the Linux kernel. Using an already compiled version of the kernel is the standard practice: as we have seen, AOSP provides exactly this kind of experience. As advanced users, we can take it a step further and build a custom kernel for our custom Android system. The Nexus family offers an easy entry into this world as we can easily obtain the kernel source code we need to build a custom version. We can also equip our custom Android system with our custom Linux kernel and we will have a full-customized ROM, tailored for our specific needs. In this book, we are using Nexus devices on purpose—Google is one of the few companies that formally make available the kernel source code. Even if every company producing and selling Android devices is forced by law to release the kernel source code, very few of them actually do it, despite all the GPL license rules. Obtaining the kernel Google provides the kernel source code and binary version for every single version of Android for every single device of the Nexus family. The following table shows where the binary version and the source code are located, ordered by device code name: Device Binary location Source location Build configuration shamu device/moto/shamu-kernel kernel/msm shamu_defconfig fugu device/asus/fugu-kernel kernel/x86_64 fugu_defconfig volantis device/htc/flounder-kernel kernel/tegra flounder_defconfig hammerhead device/lge/ hammerhead-kernel kernel/msm hammerhead_defconfig flo device/asus/flo-kernel/kernel kernel/msm flo_defconfig deb device/asus/flo-kernel/kernel kernel/msm flo_defconfig manta device/samsung/manta/kernel kernel/exynos manta_defconfig mako device/lge/mako-kernel/kernel kernel/msm mako_defconfig grouper device/asus/grouper/kernel kernel/tegra tegra3_android_defconfig tilapia device/asus/grouper/kernel kernel/tegra tegra3_android_defconfig maguro device/samsung/tuna/kernel kernel/omap tuna_defconfig toro device/samsung/tuna/kernel kernel/omap tuna_defconfig panda device/ti/panda/kernel kernel/omap panda_defconfig stingray device/moto/wingray/kernel kernel/tegra stingray_defconfig wingray device/moto/wingray/kernel kernel/tegra stingray_defconfig crespo device/samsung/crespo/kernel kernel/samsung herring_defconfig crespo4g device/samsung/crespo/kernel kernel/samsung herring_defconfig We are going to work with the Motorola Nexus 6, code name Shamu. Both the kernel binary version and the kernel source code are stored in a git repository. All we need to do is compose the proper URL and clone the corresponding repository. Retrieving the kernel's binary version In this section, we are going to obtain the kernel as a binary, prebuilt file. All we need is the previous table that shows every device model, with its codename and its binary location that we can use to compose the download of the URL. We are targeting Google Nexus 6, codename shamu with binary location: device/moto/shamu-kernel So, to retrieve the binary version of the Motorola Nexus 6 kernel, we need the following command: $ git clone https://android.googlesource.com/device/moto/shamu-kernel The previous command will clone the repo and place it in the shamu-kernel folder. This folder contains a file named zImage-dtb—this file is the actual kernel image that can be integrated in our ROM and flashed into our device. Having the kernel image, we can obtain the kernel version with the following command: $ $ dd if=kernel bs=1 skip=$(LC_ALL=C grep -a -b -o $'x1fx8bx08x00x00x00x00x00' kernel | cut -d ':' -f 1) | zgrep -a 'Linux version' Output: The previous screenshot shows the command output: our kernel image version is 3.10.40 and it has been compiled with GCC version 4.8 on October the the twenty-second at 22:49. Obtaining the kernel source code As for the binary version, the previous table is critical also to download the kernel source code. Targeting the Google Nexus 6, we create the download URL using the source location string for the device codename shamu: kernel/msm.git Once we have the exact URL, we can clone the GIT repository with the following command: $ git clone https://android.googlesource.com/kernel/msm.git Git will create an msm folder. The folder will be strangely empty—that's because the folder is tracking the master branch by default. To obtain the kernel for our Nexus 6, we need to switch to the proper branch. There are a lot of available branches and we can check out the list with the following command: $ git branch -a The list will show every single branch, targeting a specific Android version for a specific Nexus device. The following screenshot shows a subset of these repositories: Now that you have the branch name, for your device and your Android version, you just need to checkout the proper branch: $ git checkout android-msm-shamu-3.10-lollipop-release The following screenshot shows the expected command output: Setting up the toolchain The toolchain is the set of all the tools needed to effectively compile a specific software to a binary version, enabling the user to run it. In our specific domain, the toolchain allows us to create a system image ready to be flashed to our Android device. The interesting part is that the toolchain allows us to create a system image for an architecture that is different from our current one: odds are that we are using an x86 system and we want to create a system image targeting an ARM (Advanced RISC Machine) device. Compiling software targeting an architecture different from the one on our host system is called cross-compilation. The Internet offers a couple of handy solutions for this task—we can use the standard toolchain, available with the AOSP (Android Open Source Project) or we can use an alternative, very popular toolchain, the Linaro toolchain. Both toolchains will do the job—compile every single C/C++ file for the ARM architecture. As usual, even the toolchain is available as precompiled binary or as source code, ready to be compiled. For our journey, we are going to use the official toolchain, provided by Google, but when you need to explore this world even more, you could try out the binary version of Linaro toolchain, downloadable from www.linaro.org/download. Linaro toolchain is known to be the most optimized and performing toolchain in the market, but our goal is not to compare toolchains or stubbornly use the best or most popular one. Our goal is to create the smoothest possible experience, removing unnecessary variables from the whole building a custom Android system equation. Getting the toolchain We are going to use the official toolchain, provided by Google. We can obtain it with Android source code or downloading it separately. Having your trusted Android source code folder at hand, you can find the toolchain in the following folder: AOSP/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/ This folder contains everything we need to build a custom kernel—the compiler, the linker, and few more tools such as a debugger. If, for some unfortunate reason, you are missing the Android source code folder, you can download the toolchain using the following git command: $ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8 Preparing the host system To successfully compile our custom kernel, we need a properly configured host system. The requirements are similar to those we satisfied to build the whole Android system: Ubuntu Linux kernel source code Toolchain Fastboot Ubuntu needs a bit of love to accomplish this task: we need to install the ncurses-dev package: $ sudo apt-get install ncurses-dev Once we have all the required tools installed, we can start configuring the environment variables we need. These variables are used during the cross-compilation and can be set via the console. Fire up your trusted Terminal and launch the following commands: $ export PATH=<toolchain-path>/arm-eabi-4.8/bin:$PATH $ export ARCH=arm $ export SUBARCH=arm $ export CROSS_COMPILE=arm-eabi- Configuring the kernel Before being able to compile the kernel, we need to properly configure it. Every device in the Android repository has a specific branch with a specific kernel with a specific configuration to be applied. The table on page 2 has a column with the exact information we need—Build configuration. This information represents the parameter we need to properly configure the kernel build system. Let's configure everything for our Google Nexus 6. In your terminal, launch the following command: $ make shamu_defconfig This command will create a kernel configuration specific for your device. The following screenshot shows the command running and the final success message: Once the .config file is in place, you could already build the kernel, using the default configuration. As advanced users, we want more and that's why we will take full control of the system, digging into the kernel configuration. Editing the configuration could enable missing features or disable unneeded hardware support, to create the perfect custom kernel, and fit your needs. Luckily, to alter the kernel configuration, we don't need to manually edit the .config file. The Linux kernel provides a graphical tool that will allow you to navigate the whole configuration file structure, get documentation about the single configurable item, and prepare a custom configuration file with zero effort. To access the configuration menu, open your terminal, navigate to the kernel folder and launch the following command: $ make menuconfig The following screenshot shows the official Linux kernel configuration tool—no frills, but very effective: In the upper half of the screenshot, you can see the version of the kernel we are going to customize and a quick doc about how you can navigate all those menu items: you navigate using the arrow keys, you enter a subsection with the Enter key, you select or deselect an item using Y/N or Spacebar to toggle. With great power comes great responsibility, so be careful enabling and disabling features—check the documentation in menuconfig, check the Internet, and, most of all, be confident. A wrong configuration could cause a freeze during the boot sequence and this would force you to learn, to create a different configuration and try again. As a real-world example, we are going to enable the FTDI support. Future Technology Devices International or FTDI is a worldwide known semiconductor company, popular for its RS-232/TTL to USB devices. These devices come in very handy to communicate to embedded devices using a standard USB connection. To enable the FTDI support, you need to navigate to the right menu by following these steps: Device Drivers|USB support|USB Serial Converter support Once you reach this section, you need to enable the following item: USB FTDI Single Port Serial Driver The following screenshot shows the correctly selected item and gives you an idea of how many devices we could possibly support (this screen only shows the USB Serial Converter support): Once you have everything in place, just select Exit and save the configuration, as shown in the following screenshot: With the exact same approach, you can add every new feature you want. One important note, we added the FTDI package merging it into the kernel image. Linux kernel gives you the opportunity to make a feature available also as a module. A module is an external file, with .ko extension, that can be injected and loaded in the kernel at runtime. The kernel modules are a great and handy feature when you are working on a pure Linux system, but they are very impractical on Android. With the hope of having a modular kernel, you should code yourself the whole module loading system, adding unnecessary complexity to the system. The choice we made of having the FTDI feature inside the kernel image penalizes the image from a size point of view, but relieves us from the manual management of the module itself. That's why the common strategy is to include every new feature we want right into the kernel core. Compiling the kernel Once you have a properly configured environment and a brand new configuration file, you just need one single command to start the building process. On your terminal emulator, in the kernel source folder, launch: $ make The make command will wrap up the necessary configuration and will launch the compiling and assembling process. The duration of the process heavily depends on the performance of your system: it could be one minute or one hour. As a reference, an i5 2.40 GHz CPU with 8 GB of RAM takes 5-10 minutes to complete a clean build. This is incredibly quicker than compiling the whole AOSP image, as you can see, due to the different complexity and size of the code base. Working with non-Google devices So far, we have worked with Google devices, enjoying the Google open-source mindset. As advanced users, we frequently deal with devices that are not from Google or that are not even a smartphone. As a real-world example, we are going to use again a UDOO board: a single-board computer that supports Ubuntu or Android. For the time being, the most popular version of UDOO is the UDOO Quad and that's the version we are targeting. As for every other device, the standard approach is to trust the manufacturer's website to obtain kernel source code and any useful documentation for the process: most of all, how to properly flash the new kernel to the system. When working with a custom kernel, the procedure is quite consolidated. You need the source code, the toolchain, a few configuration steps, and, maybe, some specific software package to be installed on to your host system. When it comes to flashing the kernel, every device can have a different procedure. This depends on how the system has been designed and which tools the manufacturing team provides. Google provides fastboot to flash our images to our devices. Other manufactures usually provide tools that are similar or that can do similar things with little effort. The UDOO development team worked hard to make the UDOO board fully compatible with fastboot—instead of forcing you to adjust to their tools, they adjusted their device to work with the tools you already know. They tuned up the board's bootloader and you can now flash the boot.img using fastboot, like you were flashing a standard Google Android device. To obtain the kernel, we just need to clone a git repository. With your trusted terminal, launch the following command: $ git clone http://github.com/UDOOBoard/Kernel_Unico kernel Once we have the kernel, we need to install a couple of software packages in our Ubuntu system to be able to work with it. With the following command, everything will be installed and put in place: $ sudo apt-get install build-essential ncurses-dev u-boot-tools Time to pick a toolchain! UDOO gives you a few possibilities—you can use the same toolchain you used for the Nexus 6 or you can use the one provided by the UDOO team itself. If you decide to use the UDOO official toolchain, you can download it with a couple of terminal commands. Be sure you have already installed curl. If not, just install it with the following command: $ sudo apt-get install curl Once you have curl, you can use the following command to download the toolchain: $ curl http://download.udoo.org/files/crosscompiler/arm-fsl-linux-gnueabi.tar.gz | tar -xzf Now, you have everything in place to launch the build process: $ cd kernel $ make ARCH=arm UDOO_defconfig The following is the output: /sites/default/files/Article-Images/B04293_05_09.png The previous screenshot shows the output of the configuration process. When the default .config file is ready, you can launch the build process with the following command: $ make –j4 CROSS_COMPILE ../arm-fsl-linux-gnueabi/bin/arm-fsl-linux-gnueabi- ARCH=arm uImage modules When the build process is over, you can find the kernel image in the arch folder: $ arch/arm/boot/uImage As for the Nexus 6, we can customize the UDOO kernel using menuconfig. From the kernel source folder, launch the following command: $ make ARCH=arm menuconfig The following screenshot shows the UDOO kernel configuration menu. It's very similar to the Nexus 6 configuration menu. We have the same combination of keys to navigate, select and deselect features, and so on: Working with UDOO, the same warnings we had with the Nexus 6 apply here too—be careful while removing components from the kernel. Some of them are just meant to be there to support specific hardware, some of them, instead, are vital for the system to boot. As always, feel free to experiment, but be careful about gambling! This kind of development device makes debugging the kernel a bit easier compared to a smartphone. UDOO, as with a lot of other embedded development boards, provides a serial connection that enables you to monitor the whole boot sequence. This comes in handy if you are going to develop a driver for some hardware and you want to integrate it into your kernel or even if you are simply playing around with some custom kernel configuration. Every kernel and boot-related message will be printed to the serial console, ready to be captured and analyzed. The next screenshot shows the boot sequence for our UDOO Quad board: As you can see, there is plenty of debugging information, from the board power-on to the Android system prompt. Driver management Since version 2.6.x, Linux gives the developer the opportunity to compile parts of the kernel as separated modules that can be injected into the core, to add more features at runtime. This approach gives flexibility and freedom: there is no need to reboot the system to enjoy new features and there is no need to rebuild the whole kernel if you only need to update a specific module. This approach is widely use in the PC world, by embedded devices such as routers, smart TVs, and even by our familiar UDOO board. To code a new kernel module is no easy task and it's far from the purpose of this book: there are plenty of books on the topic and most of the skill set comes from experience. In these pages, you are going to learn about the big picture, the key points, and the possibilities. Unfortunately, Android doesn't use this modular approach: every required feature is built in a single binary kernel file, for practical and simplicity reasons. In the last few years there has been a trend to integrate into the kernel even the logic needed for Wi-Fi functionality, that was before it was loaded from a separated module during the boot sequence. As we saw with the FTDI example in the previous pages, the most practical way to add a new driver to our Android kernel is using menuconfig and building the feature as a core part of the kernel. Altering the CPU frequency Overclocking a CPU is one of the most loved topics among advanced users. The idea of getting the maximum amount of powerfrom your device is exciting. Forums and blogs are filled with discussions about overclocking and in this section we are going to have an overview and clarify a few tricky aspects that you could deal with on your journey. Every CPU is designed to work with a specific clock frequency or within a specific frequency range. Any modern CPU has the possibility to scale its clock frequency to maximize performance when needed and power consumption when performance is not needed, saving precious battery in case of our beloved mobile devices. Overclocking, then, denotes the possibility to alter this working clock frequency via software, increasing it to achieve performance higher than the one the CPU was designed for. Contrary to what we often read on unscrupulous forum threads or blogs, overclocking a CPU can be a very dangerous operation: we are forcing the CPU to work with a clock frequency that formally hasn't been tested. This could backfire on us with a device rebooting autonomously, for its own protection, or we could even damage the CPU, in the worst-case scenario. Another interesting aspect of managing the CPU clock frequency is the so-called underclock. Leveraging the CPU clock frequency scaling feature, we can design and implement scaling policies to maximize the efficiency, according to CPU load and other aspects. We could, for instance, reduce the frequency when the device is idle or in sleep mode and push the clock to the maximum when the device is under heavy load, to enjoy the maximum effectiveness in every scenario. Pushing the CPU management even further, lots of smartphone CPUs come with a multicore architecture: you can completely deactivate a core if the current scenario doesn't need it. The key concept of underclocking a CPU is adding a new frequency below the lowest frequency provided by the manufacturer. Via software, we would be able to force the device to this frequency and save battery. This process is not riskless. We could create scenarios in which the device has a CPU frequency so low that it will result in an unresponsive device or even a frozen device. As for overclocking, these are unexplored territories and only caution, experience and luck will get you to a satisfying result. An overview of the governors Linux kernel manages CPU scaling using specific policies called governors. There are a few pre-build governors in the Linux kernel, already available via menuconfig, but you can also add custom-made governors, for your specific needs. The following screenshot shows the menuconfig section of Google Nexus 6 for CPU scaling configuration: As you can see, there are six prebuild governors. Naming conventions are quite useful and make names self-explanatory: for instance, the performance governor aims to keep the CPU always at maximum frequency, to achieve the highest performance at every time, sacrificing battery life. The most popular governors on Android are definitely the ondemand and interactive governors: these are quite common in many Android-based device kernels. Our reference device, Google Nexus 6, uses interactive as the default governor. As you would expect, Google disallows direct CPU frequency management, for security reasons. There is no quick way to select a specific frequency or a specific governor on Android. However, advanced users can satisfy their curiosity or their needs with a little effort. Customizing the boot image So far, you learned how to obtain the kernel source code, how to set up the system, how to configure the kernel, and how to create your first custom kernel image. The next step is about equipping your device with your new kernel. To achieve this, we are going to analyze the internal structure of the boot.img file used by every Android device. Creating the boot image A custom ROM comes with four .img files, necessary to create a working Android system. Two of them (system.img and data.img) are compressed images of a Linux compatible filesystem. The remaining two files (boot.img and recovery.img) don't contain a standard filesystem. Instead, they are custom image files, specific to Android. These images contain a 2KB header sector, the kernel core, compressed with gzip, a ramdisk, and an optional second stated loader. Android provides further info about the internal structure of the image file in the boot.img.h file contained in the mkbootimg package in the AOSP source folder. The following screenshot shows a snippet of the content of this file: As you can see, the image contains a graphical representation of the boot.img structure. This ASCII art comes with a deeper explanation of sizes and pages. To create a valid boot.img file, you need the kernel image you have just built and a ramdisk. A ramdisk is a tiny filesystem that is mounted into the system RAM during the boot time. A ramdisk provides a set of critically important files, needed for a successful boot sequence. For instance, it contains the init file that is in charge of launching all the services needed during the boot sequence. There are two main ways to generate a boot image: We could use the mkbootimg tool We could use the Android build system Using mkbootimg gives you a lot of freedom, but comes with a lot of complexity. You would need a serious amount of command-line arguments to properly configure the generating system and create a working image. On the other hand, the Android build system comes with the whole set of configuration parameters already set and ready to go, with zero effort for us to create a working image. Just to give you a rough idea of the complexity of mkbootimg, the following screenshot shows an overview of the required parameters: Playing with something so powerful is tempting, but, as you can see, the amount of possible wrong parameters passed to mkbootimg is large. As pragmatic developers, dealing with mkbootimg is not worth the risk at the moment. We want the job done, so we are going to use the Android build system to generate a valid boot image with no effort. All that you need to do is export a new environment variable, pointing to the kernel image you have created just a few pages ago. With your trusted terminal emulator, launch: $ export TARGET_PREBUILT_KERNEL=<kernel_src>/arch/arm/boot/zImage-dtb Once you have set and exported the TARGET_PREBUILT_KERNEL environment variable, you can launch: $ make bootimage A brand new, fully customized, boot image will be created by the Android build system and will be placed in the following folder: $ target/product/<device-name>/boot.img With just a couple of commands, we have a brand new boot.img file, ready to be flashed. Using the Android build system to generate the boot image is the preferred way for all the Nexus devices and for all those devices, such as the UDOO, that are designed to be as close as possible to an official Google device. For all those devices on the market that are compliant to this philosophy, things start to get tricky, but not impossible. Some manufactures take advantage of the Apache v2 license and don't provide the whole Android source code. You could find yourself in a scenario where you only have the kernel source code and you won't be able to leverage the Android build system to create your boot image or even understand how boot.img is actually structured. In these scenarios, one possible approach could be to pull the boot.img from a working device, extract the content, replace the default kernel with your custom version, and recreate boot.img using mkbootimg: easier said than done. Right now, we want to focus on the main scenario, dealing with a system that is not fighting us. Upgrading the new boot image Once you have your brand new, customized boot image, containing your customized kernel image, you only need to flash it to your device. We are working with Google devices or, at least, Google-compatible devices, so you will be able to use fastboot to flash your boot.img file to your device. To be able to flash the image to the device, you need to put the device in fastboot mode, also known as bootloader mode. Once your device is in fastboot mode, you can connect it via USB to your host computer. Fire up a terminal emulator and launch the command to upgrade the boot partition: $ sudo fastboot flash boot boot.img In a few seconds, fastboot will replace the content of the device boot partition with the content of your boot.img file. When the flashing process is successfully over, you can reboot your device with: $ sudo fastboot reboot The device will reboot using your new kernel and, thanks to the new USB TTL support that you added a few pages ago, you will be able to monitor the whole boot sequence with your terminal emulator. Android boot sequence To fully understand all Android internals, we are going to learn how the whole boot sequence works: from the power-on to the actual Android system boot. The Android boot sequence is similar to any other embedded system based on Linux: in a very abstract way, after the power-on, the system initializes the hardware, loads the kernel, and finally the Android framework. Any Linux-based system undergoes a similar process during its boot sequence: your Ubuntu computer or even your home DSL router. In the next sections, we are going to dive deeper in to these steps to fully comprehend the operating system we love so much. Internal ROM – bios When you press the power button on your device, the system loads a tiny amount of code, stored inside a ROM memory. You can think about this as an equivalent of the BIOS software you have in your PC. This software is in charge of setting up all the parameters for CPU clock and running the RAM memory check. After this, the system loads the bootloader into memory and launches it. An overview of bootloader So far, the bootloader has been loaded into the RAM memory and started. The bootloader is in charge of loading the system kernel into the RAM memory and launching it, to continue the boot sequence. The most popular bootloader software for Android devices is U-Boot, the Universal Bootloader. U-Boot is widely used in all kinds of embedded systems: DSL routers, smart TVs, infotainment systems, for example. U-boot is open source software and its flexibility to be customized for any device is definitely one of the reasons for its popularity. U-boot's main task is to read the kernel image from the boot partition, load it into the RAM memory, and run it. From this moment on, the kernel is in charge of finishing the boot sequence. You could think about U-boot on Android like GRUB on your Ubuntu system: it reads the kernel image, decompresses it, loads it into the RAM memory, and executes it. The following diagram gives you a graphical representation of the whole boot sequence as on an embedded Linux system, an Android system, and a Linux PC: The kernel After the bootloader loads the kernel, the kernel's first task is to initialize the hardware. With all the necessary hardware properly set up, the kernel mounts the ramdisk from boot.img and launches init. The Init process In a standard Linux system, the init process takes care of starting all the core services needed to boot the system. The final goal is to complete the boot sequence and start the graphical interface or the command line to make the system available to the user. This whole process is based on a specific sequence of system scripts, executed in a rigorous order to assure system integrity and proper configuration. Android follows the same philosophy, but it acts in a different way. In a standard Android system, the ramdisk, contained in the boot.img, provides the init script and all the scripts necessary for the boot. The Android init process consists of two main files: init.rc init.${ro.hardware}.rc The init.rc file is the first initialization script of the system. It takes care of initializing those aspects that are common to all Android systems. The second file is very hardware specific. As you can guess, ${ro.hardware} is a placeholder for the reference of a particular hardware where the boot sequence is happening. For instance, ${ro.hardware} is replaced with goldfinsh in the emulator boot configuration. In a standard Linux system, the init sequence executes a set of bash scripts. These bash scripts start a set of system services. Bash scripting is a common solution for a lot of Linux systems, because it is very standardized and quite popular. Android systems use a different language to deal with the initialization sequence: Android Init Language. The Android init language The Android team chose to not use Bash for Android init scripts, but to create its own language to perform configurations and services launches. The Android Init Language is based on five classes of statements: Actions Commands Services Options Imports Every statement is line-oriented and is based on specific tokens, separated by white spaces. Comment lines start with a # symbol. Actions An Action is a sequence of commands bound to a specific trigger that's used to execute the particular action at a specific moment. When the desired event happens, the Action is placed in an execution queue, ready to be performed. This snippet shows an example of an Action statement: on <trigger> [&& <trigger>]* <command> <command> <command> Actions have unique names. If a second Action is created with the same name in the same file, its set of commands is added to the first Action commands, set and executed as a single action. Services Services are programs that the init sequence will execute during the boot. These services can also be monitored and restarted if it's mandatory they stay up. The following snippet shows an example of a service statement: service <name> <pathname> [ <argument> ]* <option> <option> ... Services have unique names. If in the same file, a service with a nonunique name exists, only the first one is evaluated as valid; the second one is ignored and the developer is notified with an error message. Options Options statements are coupled with services. They are meant to influence how and when init manages a specific service. Android provides quite an amount of possible options statements: critical: This specifies a device-critical service. The service will be constantly monitored and if it dies more than four times in four minutes, the device will be rebooted in Recovery Mode. disabled: This service will be in a default stopped state. init won't launch it. A disabled service can only be launched manually, specifying it by name. setenv <name> <value>: This sets an environment variable using name and value. socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]: This command creates a Unix socket, with a specified name, (/dev/socket/<name>) and provides its file descriptor the specified service. <type> specifies the type of socket: dgram, stream, or seqpacket. Default <user> and <group> are 0. <seclabel> specifies the SELinx security context for the created socket. user <username>: This changes the username before the service is executed. The default username is root. group <groupname> [ <groupname> ]*: This changes the group name before the service is executed. seclabel <seclabel>: This changes the SELinux level before launching the service. oneshot: This disables the service monitoring and the service won't be restarted when it terminates. class <name>: This specifies a service class. Classes of services can be launched or stopped at the same time. A service with an unspecified class value will be associated to the default class. onrestart: This executes a command when the service is restarted. writepid <file...>: When a services forks, this option will write the process ID (PID) in a specified file. Triggers Triggers specify a condition that has to be satisfied to execute a particular action. They can be event triggersor property triggers. Event triggers can be fired by the trigger command or by the QueueEventTrigger() function. The example event triggers are boot and late-init. Property triggers can be fired when an observed property changes value. Every Action can have multiple Property triggers, but only one Event trigger; refer to the following code for instance: on boot && property_a=b This Action will be executed when the boot event is triggered and the property a is equal to b. Commands The Command statement specifies a command that can be executed during the boot sequence, placing it in the init.rc file. Most of these commands are common Linux system commands. The list is quite extensive. Let's look at them in detail: bootchart_init: This starts bootchart if it is properly configured. Bootchart is a performance monitor and can provide insights about the boot performance of a device. chmod <octal-mode-permissions> <filename>: This changes file permissions. chown <owner> <group> <filename>: This changes the owner and the group for the specified file. class_start <serviceclass>: This starts a service specified by its class name. class_stop <serviceclass>: This stops and disables a service specified by its class name. class_reset <serviceclass>: This stops a service specified by its class name. It doesn't disable the service. copy <src> <dst>: This copies a source file to a new destination file. domainname <name>: This sets the domain name. enable <servicename>: This starts a service by its name. If the service is already queued to be started, then it starts the service immediately. exec [<seclabel>[<user>[<group> ]* ]] -- <command> [ <argument> ]*: This forks and executes the specified command. The execution is blocking: no other command can be executed in the meantime. export <name> <value>: This sets and exports an environment variable. hostname <name>: This sets the hostname. ifup <interface>: This enables the specified network interface. insmod <path>: This loads the specified kernel module. load_all_props: This loads all the system properties. load_persist_props: This loads the persistent properties, after the successful decryption of the /data partition. loglevel <level>: This sets the kernel log level. mkdir <path> [mode] [owner] [group]: This creates a folder with the specified name, permissions, owner, and group. The defaults are 755 as permissions, and root as owner and group. mount_all <fstab>: This mounts all the partitions in the fstab file. mount <type> <device> <dir> [ <flag> ]* [<options>]: This mounts a specific device in a specific folder. A few mount flags are available: rw, ro, remount, noatime, and all the common Linux mount flags. powerctl: This is used to react to changes of the sys.powerctl system parameter, critically important for the implementation of the reboot routing. restart <service>: This restarts the specified service. rm <filename>: This deletes the specified file. rmdir <foldername>: This deletes the specified folder. setpropr <name> <value>: This sets the system property with the specified name with the specified value. start <service>: This starts a service. stop <service>: This stops a service. swapon_all <fstab>: This enables the swap partitions specified in the fstab file. symlink <target> <path>: This creates a symbolic link from the target file to the destination path. sysclktz <mins_west_of_gtm>: This sets the system clock. trigger <event>: This programmatically triggers the specified event. wait <filename > [ <timeout> ]: This monitors a path for a file to appear. A timeout can be specified. If not, the default timeout value is 5 seconds. write <filename> <content>: This writes the specified content to the specified file. If the file doesn't exist, it creates the file. If the file already exists, it won't append the content, but it will override the whole file. Imports Imports specify all the external files that are needed in the current file and imports them: import <path> The previous snippet is an example of how the current init script can be extended, importing an external init script. path can be a single file or even a folder. In case path is a folder, all the files that exists in the first level of the specified folder will be imported. The command doesn't act recursively on folders: nested folders must be imported programmatically one by one. Summary In this article, you learned how to obtain the Linux kernel for your device, how to set up your host PC to properly build your custom kernel, how to add new features to the kernel, build it, package it, and flash it to your device. You learned how the Android boot sequence works and how to manipulate the init scripts to customize the boot sequence. Resources for Article: Further resources on this subject: Virtualization [article] Building Android (Must know) [article] Network Based Ubuntu Installations [article]
Read more
  • 0
  • 0
  • 16342
article-image-performing-sentiment-analysis-with-r-on-obamas-state-of-the-union-speeches-tutorial
Sugandha Lahoti
23 Sep 2018
16 min read
Save for later

Performing Sentiment Analysis with R on Obama's State of the Union speeches [Tutorial]

Sugandha Lahoti
23 Sep 2018
16 min read
For this article, we will take a look at former President Obama's State of the Union speeches. We will be performing Sentiment Analysis with R on Obama's State of the Union speeches.  The two main analytical goals are to build topic models on the six State of the Union speeches and then compare the first speech in 2010 and the last in January 2016 for sentence-based textual measures, such as sentiment and dispersion. This tutorial is taken from the book Mastering Machine Learning with R - Second Edition by Cory Lesmeister. In this book, you will master machine learning techniques with R to deliver insights in complex projects.  Preparing our Data and performing text transformations The primary package that we will use is tm, the text mining package. We will also need SnowballC for the stemming of the words, RColorBrewer for the color palettes in wordclouds, and the wordcloud package. Please ensure that you have these packages installed before attempting to load them: > library(tm) > library(wordcloud) > library(RColorBrewer) The data files are available for download in https://github.com/datameister66/data. Please ensure you put the text files into a separate directory because it will all go into our corpus for analysis. Download the seven .txt files, for example, sou2012.txt, into your working R directory. You can identify your current working directory and set it with these functions: > getwd() > setwd(".../data") We can now begin to create the corpus by first creating an object with the path to the speeches and then seeing how many files are in this directory and what they are named: > name <- file.path(".../text") > length(dir(name)) [1] 7 > dir(name) [1] "sou2010.txt" "sou2011.txt" "sou2012.txt" "sou2013.txt" [5] "sou2014.txt" "sou2015.txt" "sou2016.txt" We will name our corpus docs and create it with the Corpus() function, wrapped around the directory source function, DirSource(), which is also part of the tm package: > docs <- Corpus(DirSource(name)) > docs <<VCorpus>> Metadata: corpus specific: 0, document level (indexed): 0 Content: documents: 7 Note that there is no corpus or document level metadata. There are functions in the tm package to apply things such as author's names and timestamp information, among others, at both document level and corpus. We will not utilize this for our purposes. We can now begin the text transformations using the tm_map() function from the tm package. These will be the transformations that we discussed previously--lowercase letters, remove numbers, remove punctuation, remove stop words, strip out the whitespace, and stem the words: > docs <- tm_map(docs, tolower) > docs <- tm_map(docs, removeNumbers) > docs <- tm_map(docs, removePunctuation) > docs <- tm_map(docs, removeWords, stopwords("english")) > docs <- tm_map(docs, stripWhitespace) At this point, it is a good idea to eliminate unnecessary words. For example, during the speeches, when Congress applauds a statement, you will find (Applause) in the text. This must be removed: > docs <- tm_map(docs, removeWords, c("applause", "can", "cant", "will", "that", "weve", "dont", "wont", "youll", "youre")) After completing the transformations and removal of other words, make sure that your documents are plain text, put it in a document-term matrix, and check the dimensions: > docs = tm_map(docs, PlainTextDocument) > dtm = DocumentTermMatrix(docs) > dim(dtm) [1] 7 4738 The six speeches contain 4738 words. It is optional, but one can remove the sparse terms with the removeSparseTerms() function. You will need to specify a number between zero and one where the higher the number, the higher the percentage of sparsity in the matrix. Sparsity is the relative frequency of a term in the documents. So, if your sparsity threshold is 0.75, only terms with sparsity greater than 0.75 are removed. For us that would be (1 - 0.75) * 7, which is equal to 1.75.  Therefore, any term in fewer than two documents would be removed: > dtm <- removeSparseTerms(dtm, 0.75) > dim(dtm) [1] 7 2254 As we don't have the metadata on the documents, it is important to name the rows of the matrix so that we know which document is which: > rownames(dtm) <- c("2010", "2011", "2012", "2013", "2014", "2015", "2016") Using the inspect() function, you can examine the matrix. Here, we will look at the seven rows and the first five columns: > inspect(dtm[1:7, 1:5]) Terms Docs abandon ability able abroad absolutely 2010 0 1 1 2 2 2011 1 0 4 3 0 2012 0 0 3 1 1 2013 0 3 3 2 1 2014 0 0 1 4 0 2015 1 0 1 1 0 2016 0 0 1 0 0 It appears that our data is ready for analysis, starting with looking at the word frequency counts. Let me point out that the output demonstrates why I've been trained to not favor wholesale stemming. You may be thinking that 'ability' and 'able' could be combined. If you stemmed the document you would end up with 'abl'. How does that help the analysis? I think you lose context, at least in the initial analysis. Again, I recommend applying stemming thoughtfully and judiciously. Data Modeling and evaluation Modeling will be broken into two distinct parts. The first will focus on word frequency and correlation and culminate in the building of a topic model. In the next portion, we will examine many different quantitative techniques by utilizing the power of the qdap package in order to compare two different speeches. Word frequency and topic models As we have everything set up in the document-term matrix, we can move on to exploring word frequencies by creating an object with the column sums, sorted in descending order. It is necessary to use as.matrix() in the code to sum the columns. The default order is ascending, so putting - in front of freq will change it to descending: > freq <- colSums(as.matrix(dtm)) > ord <- order(-freq)  We will examine the head and tail of the object with the following code: > freq[head(ord)] new america people jobs now years 193 174 168 163 157 148 > freq[tail(ord)] wright written yearold youngest youngstown zero 2 2 2 2 2 2 The most frequent word is new and, as you might expect, the president mentions america frequently. Also, notice how important employment is with the frequency of jobs. I find it interesting that he mentions Youngstown, for Youngstown, OH, a couple of times. To look at the frequency of the word frequency, you can create tables, as follows: > head(table(freq)) freq 2 3 4 5 6 7 596 354 230 141 137 89 > tail(table(freq)) freq 148 157 163 168 174 193 1 1 1 1 1 1 What these tables show is the number of words with that specific frequency. So 354 words occurred three times; and one word, new in our case, occurred 193 times. Using findFreqTerms(), we can see which words occurred at least 125 times: > findFreqTerms(dtm, 125) [1] "america" "american" "americans" "jobs" "make" "new" [7] "now" "people" "work" "year" "years" You can find associations with words by correlation with the findAssocs() function. Let's look at jobs as two examples using 0.85 as the correlation cutoff: > findAssocs(dtm, "jobs", corlimit = 0.85) $jobs colleges serve market shouldnt defense put tax came 0.97 0.91 0.89 0.88 0.87 0.87 0.87 0.86 For visual portrayal, we can produce wordclouds and a bar chart. We will do two wordclouds to show the different ways to produce them: one with a minimum frequency and the other by specifying the maximum number of words to include. The first one with a minimum frequency also includes code to specify the color. The scale syntax determines the minimum and maximum word size by frequency; in this case, the minimum frequency is 70: > wordcloud(names(freq), freq, min.freq = 70, scale = c(3, .5), colors = brewer.pal(6, "Dark2")) The output of the preceding command is as follows: One can forgo all the fancy graphics, as we will in the following image, capturing the 25 most frequent words: > wordcloud(names(freq), freq, max.words = 25)   The output of the preceding command is as follows: To produce a bar chart, the code can get a bit complicated, whether you use base R, ggplot2, or lattice. The following code will show you how to produce a bar chart for the 10 most frequent words in base R: > freq <- sort(colSums(as.matrix(dtm)), decreasing = TRUE) > wf <- data.frame(word = names(freq), freq = freq) > wf <- wf[1:10, ] > barplot(wf$freq, names = wf$word, main = "Word Frequency", xlab = "Words", ylab = "Counts", ylim = c(0, 250)) The output of the preceding command is as follows: We will now move on to building topic models using the topicmodels package, which offers the LDA() function. The question now is how many topics to create. It seems logical to solve for three  topics (k=3). Certainly, I encourage you to try other numbers of topics: > library(topicmodels) > set.seed(123) > lda3 <- LDA(dtm, k = 3, method = "Gibbs") > topics(lda3) 2010 2011 2012 2013 2014 2015 2016 2 1 1 1 3 3 2 We can see an interesting transition over time. The first and last addresses have the same topic grouping, almost as if he opened and closed his tenure with the same themes. Using the terms() function produces a list of an ordered word frequency for each topic. The list of words is specified in the function, so let's look at the top 20 per topic: > terms(lda3, 25) Topic 1 Topic 2 Topic 3 [1,] "jobs" "people" "america" [2,] "now" "one" "new" [3,] "get" "work" "every" [4,] "tonight" "just" "years" [5,] "last" "year" "like" [6,] "energy" "know" "make" [7,] "tax" "economy" "time" [8,] "right" "americans" "need" [9,] "also" "businesses" "american" [10,] "government" "even" "world" [11,] "home" "give" "help" [12,] "well" "many" "lets" [13,] "american" "security" "want" [14,] "two" "better" "states" [15,] "congress" "come" "first" [16,] "country" "still" "country" [17,] "reform" "workers" "together" [18,] "must" "change" "keep" [19,] "deficit" "take" "back" [20,] "support" "health" "americans" [21,] "business" "care" "way" [22,] "education" "families" "hard" [23,] "companies" "made" "today" [24,] "million" "future" "working" [25,] "nation" "small" "good" Topic 2 covers the first and last speeches. Nothing really stands out as compelling in that topic as the others. It will be interesting to see how the next analysis can yield insights into those speeches. Topic 1 covers the next three speeches. Here, the message transitions to "jobs", "energy", "reform", and the "deficit", not to mention the comments about "education" and as we saw above, the correlation of "jobs" and "colleges". Topic 3 brings us to the next two speeches. The focus seems to really shift on to the economy and business with mentions to "security" and healthcare. In the next section, we can dig into the exact speech content further, along with comparing and contrasting the first and last State of the Union addresses. Additional quantitative analysis This portion of the analysis will focus on the power of the qdap package. It allows you to compare multiple documents over a wide array of measures. Our effort will be on comparing the 2010 and 2016 speeches. For starters, we will need to turn the text into data frames, perform sentence splitting, and then combine them into one data frame with a variable created that specifies the year of the speech. We will use this as our grouping variable in the analyses. Dealing with text data, even in R, can be tricky. The code that follows seemed to work the best, in this case, to get the data loaded and ready for analysis. We first load the qdap package. Then, to bring in the data from a text file, we will use the readLines() function from base R, collapsing the results to eliminate unnecessary whitespace. I also recommend putting your text encoding to ASCII, otherwise, you may run into some bizarre text that will mess up your analysis. That is done with the iconv() function: > library(qdap) > speech16 <- paste(readLines("sou2016.txt"), collapse=" ") Warning message: In readLines("sou2016.txt") : incomplete final line found on 'sou2016.txt' > speech16 <- iconv(speech16, "latin1", "ASCII", "") The warning message is not an issue as it is just telling us that the final line of text is not the same length as the other lines in the .txt file. We now apply the qprep() function from qdap. This function is a wrapper for a number of other replacement functions and using it will speed pre-processing, but it should be used with caution if a more detailed analysis is required. The functions it passes through are as follows: bracketX(): apply bracket removal replace_abbreviation(): replaces abbreviations replace_number(): numbers to words, for example '100' becomes 'one hundred' replace_symbol(): symbols become words, for example @ becomes 'at' > prep16 <- qprep(speech16) The other pre-processing we should do is to replace contractions (can't to cannot), remove stopwords, in our case the top 100, and remove unwanted characters, with the exception of periods and question marks. They will come in handy shortly: > prep16 <- replace_contraction(prep16) > prep16 <- rm_stopwords(prep16, Top100Words, separate = F) > prep16 <- strip(prep16, char.keep = c("?", ".")) Critical to this analysis is to now split it into sentences and add what will be the grouping variable, the year of the speech. This also creates the tot variable, which stands for Turn of Talk, serving as an indicator of sentence order. This is especially helpful in a situation where you are analyzing dialogue, say in a debate or question and answer session: > sent16 <- data.frame(speech = prep16) > sent16 <- sentSplit(sent16, "speech") > sent16$year <- "2016" Repeat the steps for the 2010 speech: > speech10 <- paste(readLines("sou2010.txt"), collapse=" ") > speech10 <- iconv(speech10, "latin1", "ASCII", "") > speech10 <- gsub("(Applause.)", "", speech10) > prep10 <- qprep(speech10) > prep10 <- replace_contraction(prep10) > prep10 <- rm_stopwords(prep10, Top100Words, separate = F) > prep10 <- strip(prep10, char.keep = c("?", ".")) > sent10 <- data.frame(speech = prep10) > sent10 <- sentSplit(sent10, "speech") > sent10$year <- "2010" Concatenate the separate years into one dataframe: > sentences <- data.frame(rbind(sent10, sent16)) One of the great things about the qdap package is that it facilitates basic text exploration, as we did before. Let's see a plot of frequent terms: > plot(freq_terms(sentences$speech)) The output of the preceding command is as follows: You can create a word frequency matrix that provides the counts for each word by speech: > wordMat <- wfm(sentences$speech, sentences$year) > head(wordMat[order(wordMat[, 1], wordMat[, 2],decreasing = TRUE),]) 2010 2016 our 120 85 us 33 33 year 29 17 americans 28 15 why 27 10 jobs 23 8 This can also be converted into a document-term matrix with the function as.dtm() should you so desire. Let's next build wordclouds, by year with qdap functionality: > trans_cloud(sentences$speech, sentences$year, min.freq = 10) The preceding command produces the following two images: Comprehensive word statistics are available. Here is a plot of the stats available in the package. The plot loses some of its visual appeal with just two speeches but is revealing nonetheless. A complete explanation of the stats is available under ?word_stats: > ws <- word_stats(sentences$speech, sentences$year, rm.incomplete = T) > plot(ws, label = T, lab.digits = 2) The output of the preceding command is as follows: Notice that the 2016 speech was much shorter, with over a hundred fewer sentences and almost a thousand fewer words. Also, there seems to be the use of asking questions as a rhetorical device in 2016 versus 2010 (n.quest 10 versus n.quest 4). To compare the polarity (sentiment scores), use the polarity() function, specifying the text and grouping variables: > pol = polarity(sentences$speech, sentences$year) > pol year total.sentences total.words ave.polarity sd.polarity stan.mean.polarity 1 2010 435 3900 0.052 0.432 0.121 2 2016 299 2982 0.105 0.395 0.267 The stan.mean.polarity value represents the standardized mean polarity, which is the average polarity divided by the standard deviation. We see that 2015 was slightly higher (0.267) than 2010 (0.121). This is in line with what we would expect, wanting to end on a more positive note. You can also plot the data. The plot produces two charts. The first shows the polarity by sentences over time and the second shows the distribution of the polarity: > plot(pol) The output of the preceding command is as follows: This plot may be a challenge to read in this text, but let me do my best to interpret it. The 2010 speech starts out with a strong negative sentiment and is slightly more negative than 2016. We can identify the most negative sentiment sentence by creating a dataframe of the pol object, find the sentence number, and produce it: > pol.df <- pol$all > which.min(pol.df$polarity) [1] 12 > pol.df$text.var[12] [1] "One year ago, I took office amid two wars, an economy rocked by a severe recession, a financial system on the verge of collapse, and a government deeply in debt. Now that is negative sentiment! Ironically, the government is even more in debt today. We will look at the readability index next: > ari <- automated_readability_index(sentences$speech, sentences$year) > ari$Readability year word.count sentence.count character.count 1 2010 3900 435 23859 2 2016 2982 299 17957 Automated_Readability_Index 1 11.86709 2 11.91929 I think it is no surprise that they are basically the same. Formality analysis is next. This takes a couple of minutes to run in R: > form <- formality(sentences$speech, sentences$year) > form year word.count formality 1 2016 2983 65.61 2 2010 3900 63.88 This looks to be very similar. We can examine the proportion of the parts of the speech. A plot is available, but adds nothing to the analysis, in this instance: > form$form.prop.by year word.count noun adj prep articles pronoun 1 2010 3900 44.18 15.95 3.67 0 4.51 2 2016 2982 43.46 17.37 4.49 0 4.96 verb adverb interj other 1 23.49 7.77 0.05 0.38 2 21.73 7.41 0.00 0.57 Now, the diversity measures are produced. Again, they are nearly identical. A plot is also available, (plot(div)), but being so similar, it once again adds no value. It is important to note that Obama's speechwriter for 2010 was Jon Favreau, and in 2016, it was Cody Keenan: > div <- diversity(sentences$speech, sentences$year) > div year wc simpson shannon collision berger_parker brillouin 1 2010 3900 0.998 6.825 5.970 0.031 6.326 2 2015 2982 0.998 6.824 6.008 0.029 6.248 One of my favorite plots is the dispersion plot. This shows the dispersion of a word throughout the text. Let's examine the dispersion of "jobs", "families", and "economy": > dispersion_plot(sentences$speech, rm.vars = sentences$year, c("security", "jobs", "economy"), color = "black", bg.color = "white") The output of the preceding command is as follows: This completes our analysis of the two speeches.  The analysis showed that, although the speeches had a similar style, the core messages changed over time as the political landscape changed. This extract is taken from the book Mastering Machine Learning with R - Second Edition. Read the book to know more advanced prediction, algorithms, and learning methods with R. Understanding Sentiment Analysis and other key NLP concepts Twitter Sentiment Analysis Sentiment Analysis of the 2017 US elections on Twitter
Read more
  • 0
  • 0
  • 16337

article-image-overview-horizon-view-architecture-and-its-components
Packt
27 Mar 2015
31 min read
Save for later

An Overview of Horizon View Architecture and its Components

Packt
27 Mar 2015
31 min read
In this article by Peter von Oven and Barry Coombs, authors of the book Mastering VMware Horizon 6, we will introduce you to the architecture and architectural components that make up the core VMware Horizon solution, concentrating on the virtual desktop elements of Horizon with Horizon View Standard. This article will cover the core Horizon View functionality of brokering virtual desktop machines that are hosted on the VMware vSphere platform. In this article, we will discuss the role of each of the Horizon View components and explain how they fit into the overall infrastructure and the benefits they bring, followed by a deep-dive into how Horizon View works. (For more resources related to this topic, see here.) Introducing the key Horizon components To start with, we are going to introduce, at a high level, the main components that make up the Horizon View product. All of the VMware Horizon components described are included as part of the licensed product, and the features that are available to you depend on whether you have the View Standard Edition, the Advanced Edition, or the Enterprise Edition. Horizon licensing also includes ESXi and vCenter licensing to support the ability to deploy the core hosting infrastructure. You can deploy as many ESXi hosts and vCenter Servers as you require to host the desktop infrastructure. The key elements of Horizon View are outlined in the following diagram: In the next section, we are going to start drilling down deeper into the architecture of how these high-level components fit together and how they work. A high-level architectural overview In this article, we will cover the core Horizon View functionality of brokering virtual desktop machines that are hosted on the VMware vSphere platform. The Horizon View architecture is pretty straightforward to understand, as its foundations lie in the standard VMware vSphere products (ESXi and vCenter). So, if you have the necessary skills and experience of working with this platform, then you are already halfway there. Horizon View builds on the vSphere infrastructure, taking advantage of some of the features of the ESX hypervisor and vCenter Server. Horizon View requires adding a number of virtual machines to perform the various View roles and functions. An overview of the View architecture is shown in the following diagram: View components run as applications that are installed on the Microsoft Windows Server operating system, so they could actually run on physical hardware as well. However, there are a great number of benefits available when you run them as virtual machines, such as delivering HA and DR, as well as the typical cost savings that can be achieved through virtualization. The following sections will cover each of these roles/components of the View architecture in greater detail. The Horizon View Connection Server The Horizon View Connection Server, sometimes referred to as Connection Broker or View Manager, is the central component of the View infrastructure. Its primary role is to connect a user to their virtual desktop by means of performing user authentication and then delivering the appropriate desktop resources based on the user's profile and user entitlement. When logging on to your virtual desktop, it is the Connection Server that you are communicating with. How does the Connection Server work? A user typically connects to their virtual desktop from their device by launching the View Client. Once the View Client has launched, the user enters the address details of the View Connection Server, which in turn responds by asking them to provide their network login details (their Active Directory (AD) domain username and password). It's worth noting that Horizon View now supports different AD function levels. These are detailed in the following screenshot: Based on their entitlements, these credentials are authenticated with AD and, if successful, the user is able to continue the logon process. Depending on what they are entitled to, the user could see a launch screen that displays a number of different desktop shortcuts available for login. These desktops represent the desktop pools that the user has been entitled to use. A pool is basically a collection of virtual desktops; for example, it could be a pool for the marketing department where the desktops contain specific applications/software for that department. Once authenticated, the View Manager makes a call to the vCenter Server to create a virtual desktop machine and then vCenter makes a call to View Composer (if you are using linked clones) to start the build process of the virtual desktop if there is not one already available. Once built, the virtual desktop is displayed/delivered within the View Client window, using the chosen display protocol (PCoIP or RDP). The process is described in detail in the following diagram: There are other ways to deploy VDI solutions that do not require a connection broker, and allow a user to connect directly to a virtual desktop; fact, there might be a specific use case for doing this such as having a large number of branches, where having local infrastructure allows trading to continue in the event of a WAN outage or poor network communication with the branch. VMware has a solution for what's referred to as a "Brokerless View": the VMware Horizon View Agent Direct-Connection Plugin. However, don't forget that, in a Horizon View environment, the View Connection Server provides greater functionality and does much more than just connecting users to desktops. The Horizon View Connection Server runs as an application on a Windows Server that, which in turn, could either be a physical or a virtual machine. Running as a virtual machine has many advantages; for example, it means that you can easily add high-availability features, which are key if you think about them, as you could potentially have hundreds of virtual user desktops running on a single-host server. Along with managing the connections for the users, the Connection Server also works with vCenter Server to manage the virtual desktop machines. For example, when using linked clones and powering on virtual desktops, these tasks might be initiated by the Connection Server, but they are executed at the vCenter Server level. Minimum requirements for the Connection Server To install the View Connection Server, you need to meet the following minimum requirements to run on physical or virtual machines: Hardware requirements: The following screenshot shows the hardware required:< Supported operating systems: The View Connection Server must be installed on one of the following operating systems: The Horizon View Security Server Horizon View Security Server is another instance and another version of the View Connection Server but, this time, it sits within your DMZ so that you can allow end users to securely connect to their virtual desktop machine from an external network or the Internet. You cannot install the View Security Server on the same machine that is already running as a View Connection Server or any of the other Horizon View components. How does the Security Server work? The user login process at the start is the same as when using a View Connection Server for internal access but, now we have added an extra security layer with the Security Server. The idea is that users can access their desktop externally without unnecessarily needing a VPN on the network first. The process is described in detail in the following diagram: The Security Server is paired with a View Connection Server that is configured by the use of a one-time password during installation. It's a bit like pairing your phone's Bluetooth with the hands-free kit in your car. When the user logs in from the View Client, they access the View Connection Server, which in turn authenticates the user against AD. If the View Connection Server is configured as a PCoIP gateway, then it will pass the connection and addressing information to the View Client. This connection information will allow the View Client to connect to the View Security Server using PCoIP. This is shown in the diagram by the green arrow (1). The View Security Server will then forward the PCoIP connection to the virtual desktop machine, (2) creating the connection for the user. The virtual desktop machine is displayed/delivered within the View Client window (3) using the chosen display protocol (PCoIP or RDP). The Horizon View Replica Server The Horizon View Replica Server, as the name suggests, is a replica or copy of a View Connection Server that is used to enable high availability to your Horizon View environment. Having a replica of your View Connection Server means that, if the Connection Server fails, users are still able to connect to their virtual desktop machines. You will need to change the IP address or update the DNS record to match this server if you are not using a load balancer. How does the Replica Server work? So, the first question is, what actually gets replicated? The View Connection Broker stores all its information relating to the end users, desktop pools, virtual desktop machines, and other View-related objects, in an Active Directory Application Mode (ADAM) database. Then, using the Lightweight Directory Access Protocol (LDAP) (it uses a method similar to what AD uses for replication), this View information gets copied from the original View Connection Server to the Replica Server. As both, the Connection Server and the Replica Server are now identical to each other, if your Connection Server fails, then you essentially have a backup that steps in and takes over so that end users can still continue to connect to their virtual desktop machines. Just like with the other components, you cannot install the Replica Server role on the same machine that is running as a View Connection Server or any of the other Horizon View components. Persistent or nonpersistent desktops In this section, we are going to talk about the different types of desktop assignments that can be deployed with Horizon View; these could also potentially have an impact on storage requirements, and also the way in which desktops are provisioned to the end users. One of the questions that always get asked is about having a dedicated (persistent) or a floating desktop assignment (nonpersistent). Desktops can either be individual virtual machines, which are dedicated to a user on a 1:1 basis (as we have in a physical desktop deployment, where each user effectively has their own desktop), or a user has a new, vanilla desktop that gets provisioned, personalized, and then assigned at the time of login and can be chosen at random from a pool of available desktops. This is the model that is used to build the user's desktop. The two options are described in more detail as follows: Persistent desktop: Users are allocated a desktop that retains all of their documents, applications, and settings between sessions. The desktop is statically assigned the first time that the user connects and is then used for all subsequent sessions. No other user is permitted access to the desktop. Nonpersistent desktop: Users might be connected to different desktops from the pool, each time that they connect. Environmental or user data does not persist between sessions and is delivered as the user logs on to their desktop. The desktop is refreshed or reset when the user logs off. In most use cases, a nonpersistent configuration is the best option, the key reason is that, in this model, you don't need to build all the desktops upfront for each user. You only need to power on a virtual desktop as and when it's required. All users start with the same basic desktop, which then gets personalized before delivery. This helps with concurrency rates. For example, you might have 5,000 people in your organization, but only 2,000 ever login at the same time; therefore, you only need to have 2,000 virtual desktops available. Otherwise, you would have to build a desktop for each one of the 5,000 users that might ever log in, resulting in more server infrastructure and certainly a lot more storage capacity. We will talk about storage in the next section. The other thing that we often see some confusion over is the difference between dedicated and floating desktops, and how linked clones fit in. Just to make it clear, linked clones and full clones are not what we are talking about when we refer to dedicated and floating desktops. Cloning operations refer to how a desktop is built, whereas the terms persistent and nonpersistent refer to how a desktop is assigned to a user. Dedicated and floating desktops are purely about user assignment and whether they have a dedicated desktop or one allocated from a pool on-demand. Linked clones and full clones are features of Horizon View, which uses View Composer to create a desktop image for each user from a master or parent image. This means, regardless of having a floating or dedicated desktop assignment, the virtual desktop machine could still be a linked or full clone. So, here's a summary of the benefits: It is operationally efficient. All users start from a single or smaller number of desktop images. Organizations reduce the amount of image and patch management. It is efficient storage-wise. The amount of storage required to host the nonpersistent desktop images will be smaller than keeping separate instances of unique user desktops. In the next section, we are going to cover an in-depth overview of Horizon View Composer and linked clones, and the advantages the technology delivers. Horizon View Composer and linked clones One of the main reasons a virtual desktop project fails to deliver, or doesn't even get out of the starting blocks, is heavy infrastructure down to storage requirements. The storage requirements are often seen as a huge cost burden, which can be attributed to the fact that people are approaching this in the same way they would approach a physical desktop environment's requirements. This would mean that each user gets their own dedicated virtual desktop and the hard disk space that comes with it, albeit a virtual disk; this then gets scaled out for the entire user population, so each user is allocated a virtual desktop with some storage. Let's take an example. If you had 1,000 users and allocated 250 GB per user's desktop, you would need 1,000 * 250 GB = 2.5 TB for the virtual desktop environment. That's a lot of storage just for desktops and could result in significant infrastructure costs that could possibly mean that the cost to deploy this amount of storage in the data center would render the project cost-in effective, compared to physical desktop deployments. A new approach to deploying storage for a virtual desktop environment is needed and this is where linked clone technology comes into play. In a nutshell, linked clones are designed to reduce the amount of disk space required, and to simplify the deployment and management of images to multiple virtual desktop machines—a centralized and much easier process. Linked clone technology Starting at a high level, a clone is a copy of an existing or parent virtual machine. This parent virtual machine (VM) is typically your gold build from which you want to create new virtual desktop machines. When a clone is created, it becomes a separate, new virtual desktop machine with its own unique identity. This process is not unique to Horizon View; it's actually a function of vSphere and vCenter, and in the case of Horizon View, we add in another component, View Composer, to manage the desktop images. There are two types of clones that we can deploy, a full clone or a linked clone. We will explain the difference in the next sections. Full clones As the name implies, a full clone disk is an exact, full-sized copy of the parent machine. Once the clone has been created, the virtual desktop machine is unique, with its own identity, and has no links back to the parent virtual machine from which it was cloned. It can operate as a fully independent virtual desktop in its own right and is not reliant on its parent virtual machine. However, as it is a full-sized copy, be aware that it will take up the same amount of storage as its parent virtual machine, which leads back to our discussion earlier in this article about storage capacity requirements. Using a full clone will require larger amounts of storage capacity and will possibly lead to higher infrastructure costs. Before you completely dismiss the idea of using full clone virtual desktop machines, there are some use cases that rely on this model. For example, if you use VMware Mirage to deliver a base layer or application layer, it only works today with full clones, dedicated Horizon View virtual desktop machines. If you have software developers, then they probably need to install specialist tools and a trust code onto a desktop, and therefore, need to "own" their desktop. Or perhaps, the applications that you run in your environment need a dedicated desktop due to the way the applications are licensed. Linked clones Having now discussed full clones, we are going to talk about deploying virtual desktop machines with linked clones. In a linked clone deployment, a delta disk is created and then used by the virtual desktop machine to store the data differences between its own operating system and the operating system of its parent virtual desktop machine. Unlike the full clone method, the linked clone is not a full copy of the virtual disk. The term linked clone refers to the fact that the linked clone will always look to its parent in order to operate, as it continues to read from the replica disk. Basically, the replica is a copy of a snapshot of the parent virtual desktop machine. The linked clone itself could potentially grow to the same size as the replica disk if you allow it to. However, you can set limits on how big it can grow, and should it start to get too big, then you can refresh the virtual desktops that are linked to it. This essentially starts the cloning process again from the initial snapshot. Immediately after a linked clone virtual desktop is deployed, the difference between the parent virtual machine and the newly created virtual desktop machine is extremely small and therefore reduces the storage capacity requirements compared to that of a full clone. This is how linked clones are more space-efficient than their full clone brothers. The underlying technology behind linked clones is more like a snapshot than a clone, but with one key difference: View Composer. With View Composer, you can have more than one active snapshot linked to the parent virtual machine disk. This allows you to create multiple virtual desktop images from just one parent. Best practice would be to deploy an environment with linked clones so as to reduce the storage requirements. However, as we previously mentioned, there are some use cases where you will need to use full clones. One thing to be aware of, which still relates to the storage, is that, rather than capacity, we are now talking about performance. All linked clone virtual desktops are going to be reading from one replica and therefore, will drive a high number of Input /Output Operations Per Second (IOPS) on the storage where the replica lives. Depending on your desktop pool design, you are fairly likely to have more than one replica, as you would typically have more than one data store. This in turn depends on the number of users who will drive the design of the solution. In Horizon View, you are able to choose the location where the replica lives. One of the recommendations is that the replica should sit on fast storage such as a local SSD. Alternative solutions would be to deploy some form of storage acceleration technology to drive the IOPS. Horizon View also has its own integrated solution called View Storage Accelerator (VSA) or Content Based Read Cache (CBRC). This feature allows you to allocate up to 2 GB of memory from the underlying ESXi host server that can be used as a cache for the most commonly read blocks. As we are talking about booting up desktop operating systems, the same blocks are required; as these can be retrieved from memory, the process is accelerated. Another solution is View Composer Array Integration (VCAI), which allows the process of building linked clones to be offloaded to the storage array and its native snapshot mechanism rather than taking CPU cycles from the host server. There are also a number of other third-party solutions that resolve the storage performance bottleneck, such as Atlantis Computing and their ILIO product, Nutanix, Nimble, and Tintri to name a few others. In the next section, we will take a deeper look at how linked clones work. How do linked clones work? The first step is to create your master virtual desktop machine image, which should contain not only the operating system, core applications, and settings, but also the Horizon View Agent components. This virtual desktop machine will become your parent VM or your gold image. This image can now be used as a template to create any new subsequent virtual desktop machines. The gold image or parent image cannot be a VM template. An overview of the linked clone process is shown in the following diagram:   Once you have created the parent virtual desktop or gold image (1), you then take a snapshot (2). When you create your desktop pool, this snapshot is selected and will become the replica (3) and will be set to be read-only. Each virtual desktop is linked back to this replica; hence the term linked clone. When you start creating your virtual desktops, you create linked clones that are unique copies for each user. Try not to create too many snapshots for your parent VM. I would recommend having just a handful, otherwise this could impact the performance of your desktops and make it a little harder to know which snapshot is which. What does View Composer build? During the image building process, and once the replica disk has been created, View Composer creates a number of other virtual disks, including the linked clone (operating system disk) itself. These are described in the following sections. Linked clone disk Not wanting to state the obvious, the main disk that gets created is the linked clone disk itself. This linked clone disk is basically an empty virtual disk container that is attached to the virtual desktop machine as the user logs in and the desktop starts up. This disk will start off small in size, but will grow over time, depending on the block changes that are requested from the replica disk by the virtual desktop machine's operating system. These block changes are stored in the linked clone disk, and this disk is sometimes referred to as the delta disk, or differential disk, due to the fact that it stores all the delta changes that the desktop operating system requests from the parent VM. As mentioned before, the linked clone disk can grow to the maximum size, equal to the parent VM but, following best practice, you would never let this happen. Typically, you can expect the linked clone disk to only increase to a few hundred MBs. We will cover this in the Linked clone processes section later. The replica disk is set as read-only and is used as the primary disk. Any writes and/or block changes that are requested by the virtual desktop are written/read directly from the linked clone disk. It is a recommended best practice to allocate tier-1 storage, such as local SSD drives, to host the replica, as all virtual desktops in the cluster will be referencing this single read-only VMDK file as their base image. Keeping it high in the stack improves performance, by reducing the overall storage IOPS required in a VDI workload. As we mentioned at the start of this section, storage costs are seen as being expensive for VDI. Linked clones reduce the burden of storage capacity but they do drive the requirement to derive a huge amount of IOPS from a single LUN. Persistent disk or user data disk The persistent disk feature of View Composer allows you to configure a separate disk that contains just the user data and user settings, and not the operating system. This allows any user data to be preserved when you update or make changes to the operating system disk, such as a recompose action. It's worth noting that the persistent disk is referenced by the VM name and not username, so bear this in mind if you want to attach the disk to another VM. This disk is also used to store the user's profile. With this in mind, you need to size it accordingly, ensuring that it is large enough to store any user profile type data such as Virtual Desktop Assessments. Disposable disk With the disposable disk option, Horizon View creates what is effectively a temporary disk that gets deleted every time the user powers off their virtual desktop machine. If you think about how the Windows desktop operating system operates and the files it creates, there are several files that are used on a temporary basis. Files such as Temporary Internet files or the Windows pagefile are two such examples. As these are only temporary files, why would you want to keep them? With Horizon View, these type of files are redirected to the disposable disk and then deleted when the VM is powered off. Horizon View provides the option to have a disposable disk for each virtual desktop. This disposable disk is used to contain temporary files that will get deleted when the virtual desktop is powered off. These are files that you don't want to store on the main operating system disk as they would consume unnecessary disk space. For example, files on the disposable disk are things such as the pagefile, Windows system temporary files, and VMware log files. Note that here we are talking about temporary system files and not user files. A user's temporary files are still stored on the user data disk so that they can be preserved. Many applications use the Windows temp folder to store installation CAB files, which can be referenced post-installation. Having said that, you might want to delete the temporary user data to reduce the desktop image size, in which case you could ensure that the user's temporary files are directed to the disposable disk. Internal disk Finally, we have the internal disk. The internal disk is used to store important configuration information, such as the computer account password, that would be needed to join the virtual desktop machine back to the domain if you refreshed the linked clones. It is also used to store Sysprep and Quickprep configurations details. In terms of disk space, the internal disk is relatively small, averaging around 20 MB. By default, the user will not see this disk from their Windows Explorer, as it contains important configuration information that you wouldn't want them to delete. Understanding the linked clone process There are several complex steps performed by View Composer and View Manager and that occur when a user launches a virtual desktop session. So, what's the process to build a linked clone desktop, and what goes on behind the scenes? When a user logs into Horizon View and requests a desktop, View Manager, using vCenter and View Composer, will create a virtual desktop machine. This process is described in the following sections. Creating and provisioning a new desktop An entry for the virtual desktop machine is created in the Active Directory Application Mode (ADAM) database before it is put into provisioning mode: The linked clone virtual desktop machine is created by View Composer. A machine account created in AD with a randomly generated password. View Composer checks for a replica disk and creates one if one does not already exist. A linked clone is created by the vCenter Server API call from View Composer. An internal disk is created to store the configuration information and machine account password. Customizing the desktop Now that you have a newly created, linked clone virtual desktop machine, the next phase is to customize it. The customization steps are as follows: The virtual desktop machine is switched to customization mode. The virtual desktop machine is customized by vCenter Server using the customizeVM_Task command and is joined to the domain with the information you entered in the View Manager console. The linked clone virtual desktop is powered on. The View Composer Agent on the linked clone virtual desktop machine starts up for the first time and joins the machine to the domain, using the NetJoinDomain command and the machine account password that was created on the internal disk. The linked clone virtual desktop machine is now Sysprep'd. Once complete, View Composer tells View Agent that customization has finished, and View Agent tells View Manager that the customization process has finished. The linked clone virtual desktop machine is powered off and a snapshot is taken. The linked clone virtual desktop machine is marked as provisioned and is now available for use. When a linked clone virtual desktop machine is powered on with the View Composer Agent running, the agent tracks any changes that are made to the machine account password. Any changes will be updated and stored on the internal disk. In many AD environments, the machine account password is changed periodically. If the View Composer Agent detects a password change, it updates the machine account password on the internal disk that was created with the linked clone. This is important, as a the linked clone virtual desktop machine is reverted to the snapshot taken after the customization during a refresh operation. For example, the agent will be able to reset the machine account password to the latest one. The linked clone process is depicted in the following diagram:   Additional features and functions of linked clones There are a number of other management functions that you can perform on a linked clone disk from View Composer; these are outlined in this section and are needed in order to deliver the ongoing management of the virtual desktop machines. Recomposing a linked clone Recomposing a linked clone virtual desktop machine or desktop pool allows you to perform updates to the operating system disk, such as updating the image with the latest patches, or software updates. You can only perform updates on the same version of an operating system, so you cannot use the recompose feature to migrate from one operating system to another, such as going from Windows XP to Windows 7. As we covered in the What does View Composer Build? section, we have separate disks for items such as user's data. These disks are not affected during a recompose operation, so all user-specific data on them is preserved. When you initiate the recompose operation, View Composer essentially starts the linked clone building process over again; thus, a new operating system disk is created, which then gets customized and a snapshot, such as the ones shown in the preceding sections, is taken. During the recompose operation, the MAC addresses of the network interface and the Windows SID are not preserved. There are some management tools and security-type solutions that might not work due to this change. However, the UUID will remain the same. The recompose process is described in the following steps: View Manager puts the linked clone into maintenance mode. View Manager calls the View Composer resync API for the linked clones being recomposed, directing View Composer to use the new base image and the snapshot. If there isn't a replica for the base image and snapshot yet, in the target datastore for the linked clone, View Composer creates the replica in the target datastore (unless a separate datastore is being used for replicas, in which case a replica is created in the replica datastore). View Composer destroys the current OS disk for the linked clone and creates a new OS disk linked to the new replica. The rest of the recompose cycle is identical to the customization phase of the provisioning and customization cycles. The following diagram shows a graphical representation of the recompose process. Before the process begins, the first thing you need to do is update your Gold Image (1) with the patch updates or new applications you want to deploy as the virtual desktops. As described in the preceding steps, the snapshot is then taken (2) to create the new replica, Replica V2 (3). The existing OS disk is destroyed, but the User Data disk (4) is maintained during the recompose process:   Refreshing a linked clone By carrying out a refresh of the linked clone virtual desktop, you are effectively reverting it to its initial state, when its original snapshot was taken after it had completed the customization phase. This process only applies to the operating system disk and no other disks are affected. An example use case for refresh operations would be recomposing a nonpersistent desktop two hours after logoff, to return it to its original state and make it available for the next user. The refresh process performs the following tasks: The linked clone virtual desktop is switched into maintenance mode. View Manager reverts the linked clone virtual desktop to the snapshot taken after customization was completed: - vdm-initial-checkpoint. The linked clone virtual desktop starts up, and View Composer Agent detects if the machine account password needs to be updated. If not, and the password on the internal disk is newer than the one in the registry, the agent will update the machine account password using the one on the internal disk. One of the reasons why you would perform a refresh operation is if the linked clone OS disk starts to become bloated. As we previously discussed, the OS-linked clone disk could grow to the full size of its parent image. This means it would be taking up more disk space than is really necessary, which kind of defeats the objective of linked clones. The refresh operation effectively resets the linked clone to a small delta between it and its parent image. The following diagram shows a representation of the refresh operation:   The linked clone on the left-hand side of the diagram (1) has started to grow in size. Refreshing reverts it back to the snapshot as if it was a new virtual desktop, as shown on the right-hand side of the diagram (2). Rebalancing operations with View Composer The rebalance operation in View Composer is used to evenly distribute the linked clone virtual desktop machines across multiple datastores in your environment. You would perform this task in the event that one of your datastores was becoming full while others have ample free space. It might also help with the performance of that particular datastore. For example, if you had 10 virtual desktop machines in one datastore and only two in another, then running a rebalance operation would potentially even this out and leave you with six virtual desktop machines per datastore. You must use the View Administrator console to initiate the rebalance operation in View Composer. If you simply try to vMotion any of your virtual desktop machines, then View Composer will not be able to keep track of them. On the other hand, if you have six virtual desktop machines on one datastore and seven on another, then it is highly likely that initiating a rebalance operation will have no effect, and no virtual desktop machines will be moved, as doing so has no benefit. A virtual desktop machine will only be moved to another datastore if the target datastore has significantly more spare capacity than the source. The rebalance process is described in the following steps: The linked clone is switched to maintenance mode. Virtual machines to be moved are identified based on the free space in the available datastores. The operating system disk and persistent disk are disconnected from the virtual desktop machine. The detached operating system disk and persistent disk are moved to the target datastore. The virtual desktop machine is moved to the target datastore. The operating system disk and persistent disk are reconnected to the linked clone virtual desktop machine. View Composer resynchronizes the linked clone virtual desktop machines. View Composer checks for the replica disk in the datastore and creates one if one does not already exist as per the provisioning steps covered earlier in this article. As per the recompose operation, the operating system disk for the linked clone gets deleted and a new one is created and then customized. The following diagram shows the rebalance operation:   Summary In this article, we discussed the Horizon View architecture and the different components that make up the complete solution. We covered the key technologies, such as how linked clones work to optimize storage. Resources for Article: Further resources on this subject: Importance of Windows RDS in Horizon View [article] Backups in the VMware View Infrastructure [article] Design, Install, and Configure [article]
Read more
  • 0
  • 0
  • 16331

article-image-getting-inside-c-plus-plus-multithreaded-application
Maya Posch
13 Feb 2018
8 min read
Save for later

Getting Inside a C++ Multithreaded Application

Maya Posch
13 Feb 2018
8 min read
This C++ programming tutorial is taken from Maya Posch's Mastering C++ Multithreading. In its most basic form, a multithreaded application consists of a singular process with two or more threads. These threads can be used in a variety of ways, for example, to allow the process to respond to events in an asynchronous manner by using one thread per incoming event or type of event, or to speed up the processing of data by splitting the work across multiple threads. Examples of asynchronous response to events include the processing of user interface (GUI) and network events on separate threads so that neither type of event has to wait on the other, or can block events from being responded to in time. Generally, a single thread performs a single task, such as the processing of GUI or network events, or the processing of data. For this basic example, the application will start with a singular thread, which will then launch a number of threads, and wait for them to finish. Each of these new threads will perform its own task before finishing. Let's start with the includes and global variables for our application: #include <iostream> #include <thread> #include <mutex> #include <vector> #include <random> using namespace std; // --- Globals mutex values_mtx; mutex cout_mtx; Both the I/O stream and vector headers should be familiar to anyone who has ever used C++: the former is here used for the standard output (cout), and vector for storing a sequence of values. The random header is new in c++11, and as the name suggests, it offers classes and methods for generating random sequences. We use it here to make our threads do something interesting. Finally, the thread and mutex includes are the core of our multithreaded application; they provide the basic means for creating threads, and allow for thread-safe interactions between them. Moving on, we create two mutexes: one for the global vector and one for cout, since the latter is not thread-safe. Next we create the main function as follows: int main() { values.push_back(42); We then push a fixed value onto the vector instance; this one will be used by the threads we create in a moment: thread tr1(threadFnc, 1); thread tr2(threadFnc, 2); thread tr3(threadFnc, 3); thread tr4(threadFnc, 4); We create new threads, and provide them with the name of the method to use, passing along any parameters--in this case, just a single integer: tr1.join(); tr2.join(); tr3.join(); tr4.join(); Next, we wait for each thread to finish before we continue by calling join() on each thread instance: cout << "Input: " << values[0] << ", Result 1: " << values[1] << ", Result 2: " << values[2] << ", Result 3: " << values[3] << ", Result 4: " << values[4] << "n"; return 1; } At this point, we expect that each thread has done whatever it's supposed to do, and added the result to the vector, which we then read out and show the user. Of course, this shows almost nothing of what really happens in the application, mostly just the essential simplicity of using threads. Next, let's see what happens inside this method that we pass to each thread instance: void threadFnc(int tid) { cout_mtx.lock(); cout << "Starting thread " << tid << ".n"; cout_mtx.unlock(); When we obtain the initial value set in the vector, we copy it to a local variable so that we can immediately release the mutex for the vector to enable other threads to use it: int rval = randGen(0, 10); val += rval; These last two lines contain the essence of what the threads created do: they take the initial value, and add a randomly generated value to it. The randGen() method takes two parameters, defining the range of the returned value: cout_mtx.lock(); cout << "Thread " << tid << " adding " << rval << ". New value: " << val << ".n"; cout_mtx.unlock(); values_mtx.lock(); values.push_back(val); values_mtx.unlock(); } Finally, we (safely) log a message informing the user of the result of this action before adding the new value to the vector. In both cases, we use the respective mutex to ensure that there can be no overlap with any of the other threads. Once the method reaches this point, the thread containing it will terminate, and the main thread will have one fewer thread to wait for to rejoin. Lastly, we'll take a look at the randGen() method. Here we can see some multithreaded specific additions as well: int randGen(const int& min, const int& max) { static thread_local mt19937 generator(hash<thread::id>()(this_thread::get_id())); uniform_int_distribution<int> distribution(min, max); return distribution(generator) } This preceding method takes a minimum and maximum value as explained earlier, which limit the range of the random numbers this method can return. At its core, it uses a mt19937-based generator, which employs a 32-bit Mersenne Twister algorithm with a state size of 19937 bits. This is a common and appropriate choice for most applications. Of note here is the use of the thread_local keyword. What this means is that even though it is defined as a static variable, its scope will be limited to the thread using it. Every thread will thus create its own generator instance, which is important when using the random number API in the STL. A hash of the internal thread identifier (not our own) is used as seed for the generator. This ensures that each thread gets a fairly unique seed for its generator instance, allowing for better random number sequences. Finally, we create a new uniform_int_distribution instance using the provided minimum and maximum limits, and use it together with the generator instance to generate the random number which we return. Makefile In order to compile the code described earlier, one could use an IDE, or type the command on the command line. As mentioned in the beginning of this chapter, we'll be using makefiles for the examples in this book. The big advantages of this are that one does not have to repeatedly type in the same extensive command, and it is portable to any system which supports make. The makefile for this example is rather basic: GCC := g++ OUTPUT := ch01_mt_example SOURCES := $(wildcard *.cpp) CCFLAGS := -std=c++11 all: $(OUTPUT) $(OUTPUT): clean: rm $(OUTPUT) .PHONY: all From the top down, we first define the compiler that we'll use (g++), set the name of the output binary (the .exe extension on Windows will be post-fixed automatically), followed by the gathering of the sources and any important compiler flags. The wildcard feature allows one to collect the names of all files matching the string following it in one go without having to define the name of each source file in the folder individually. For the compiler flags, we're only really interested in enabling the c++11 features, for which GCC still requires one to supply this compiler flag. For the all method, we just tell make to run g++ with the supplied information. Next we define a simple clean method which just removes the produced binary, and finally, we tell make to not interpret any folder or file named all in the folder, but to use the internal method with the .PHONY section. When we run this makefile, we see the following command-line output: $ make Afterwards, we find an executable file called ch01_mt_example (with the .exe extension attached on Windows) in the same folder. Executing this binary will result in a command-line output akin to the following: $ ./ch01_mt_example.exe Starting thread 1. Thread 1 adding 8. New value: 50. Starting thread 2. Thread 2 adding 2. New value: 44. Starting thread 3. Starting thread 4. Thread 3 adding 0. New value: 42. Thread 4 adding 8. New value: 50. Input: 42, Result 1: 50, Result 2: 44, Result 3: 42, Result 4: 50 What one can see here already is the somewhat asynchronous nature of threads and their output. While threads 1 and 2 appear to run synchronously, threads 3 and 4 clearly run asynchronously. For this reason, and especially in longer-running threads, it's virtually impossible to say in which order the log output and results will be returned. While we use a simple vector to collect the results of the threads, there is no saying whether Result 1 truly originates from the thread which we assigned ID 1 in the beginning. If we need this information, we need to extend the data we return by using an information structure with details on the processing thread or similar. One could, for example, use struct like this: struct result { int tid; int result; }; The vector would then be changed to contain result instances rather than integer instances. One could pass the initial integer value directly to the thread as part of its parameters, or pass it via some other way. Want to learn C++ multithreading in detail? You can find Mastering C++ Multithreading here, or explore all our latest C++ eBooks and videos here.
Read more
  • 0
  • 0
  • 16318
article-image-cross-platform-solution-xamarinforms-and-mvvm-architecture
Packt
22 Feb 2016
9 min read
Save for later

A cross-platform solution with Xamarin.Forms and MVVM architecture

Packt
22 Feb 2016
9 min read
In this article by George Taskos, the author of the book, Xamarin Cross Platform Development Cookbook, we will discuss a cross-platform solution with Xamarin.Forms and MVVM architecture. Creating a cross-platform solution correctly requires a lot of things to be taken under consideration. In this article, we will quickly provide you with a starter MVVM architecture showing data retrieved over the network in a ListView control. (For more resources related to this topic, see here.) How to do it... In Xamarin Studio, click on File | New | Xamarin.Forms App. Provide the name XamFormsMVVM. Add the NuGet dependencies by right-clicking on each project in the solution and choosing Add | Add NuGet Packages…. Search for the packages XLabs.Forms and modernhttpclient, and install them. Repeat step 2 for the XamFormsMVVM portable class library and add the packages Microsoft.Net.Http and Newtonsoft.Json. In the XamFormsMVVM portable class library, create the following folders: Models, ViewModels, and Views. To create a folder, right-click on the project and select Add | New Folder. Right-click on the Models folder and select Add | New File…, choose the General | Empty Interface template, name it IDataService, and click on New, and add the following code: public interface IDataService { Task<IEnumerable<OrderModel>> GetOrdersAsync (); } Right-click on the Models folder again and select Add | New File…, choose the General | Empty Class template, name it DataService, and click on New, and add the following code: [assembly: Xamarin.Forms.Dependency (typeof (DataService))] namespace XamFormsMVVM{ public class DataService : IDataService { protected const string BaseUrlAddress = @"https://api.parse.com/1/classes"; protected virtual HttpClient GetHttpClient() { HttpClient httpClient = new HttpClient(new NativeMessageHandler()); httpClient.BaseAddress = new Uri(BaseUrlAddress); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue ("application/json")); return httpClient; } public async Task<IEnumerable<OrderModel>> GetOrdersAsync () { using (HttpClient client = GetHttpClient ()) { HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, client.BaseAddress + "/Order"); requestMessage.Headers.Add("X-Parse- Application-Id", "fwpMhK1Ot1hM9ZA4iVRj49VFz DePwILBPjY7wVFy"); requestMessage.Headers.Add("X-Parse-REST- API-Key", "egeLQVTC7IsQJGd8GtRj3ttJV RECIZgFgR2uvmsr"); HttpResponseMessage response = await client.SendAsync(requestMessage); response.EnsureSuccessStatusCode (); string ordersJson = await response.Content.ReadAsStringAsync(); JObject jsonObj = JObject.Parse (ordersJson); JArray ordersResults = (JArray)jsonObj ["results"]; return JsonConvert.DeserializeObject <List<OrderModel>> (ordersResults.ToString ()); } } } } Right-click on the Models folder and select Add | New File…, choose the General | Empty Interface template, name it IDataRepository, and click on New, and add the following code: public interface IDataRepository { Task<IEnumerable<OrderViewModel>> GetOrdersAsync (); } Right-click on the Models folder and select Add | New File…, choose the General | Empty Class template, name it DataRepository, and click on New, and add the following code in that file: [assembly: Xamarin.Forms.Dependency (typeof (DataRepository))] namespace XamFormsMVVM { public class DataRepository : IDataRepository { private IDataService DataService { get; set; } public DataRepository () : this(DependencyService.Get<IDataService> ()) { } public DataRepository (IDataService dataService) { DataService = dataService; } public async Task<IEnumerable<OrderViewModel>> GetOrdersAsync () { IEnumerable<OrderModel> orders = await DataService.GetOrdersAsync ().ConfigureAwait (false); return orders.Select (o => new OrderViewModel (o)); } } } In the ViewModels folder, right-click on Add | New File… and name it OrderViewModel. Add the following code in that file: public class OrderViewModel : XLabs.Forms.Mvvm.ViewModel { string _orderNumber; public string OrderNumber { get { return _orderNumber; } set { SetProperty (ref _orderNumber, value); } } public OrderViewModel (OrderModel order) { OrderNumber = order.OrderNumber; } public override string ToString () { return string.Format ("[{0}]", OrderNumber); } } Repeat step 5 and create a class named OrderListViewModel.cs: public class OrderListViewModel : XLabs.Forms.Mvvm.ViewModel{ protected IDataRepository DataRepository { get; set; } ObservableCollection<OrderViewModel> _orders; public ObservableCollection<OrderViewModel> Orders { get { return _orders; } set { SetProperty (ref _orders, value); } } public OrderListViewModel () : this(DependencyService.Get<IDataRepository> ()) { } public OrderListViewModel (IDataRepository dataRepository) { DataRepository = dataRepository; DataRepository.GetOrdersAsync ().ContinueWith (antecedent => { if (antecedent.Status == TaskStatus.RanToCompletion) { Orders = new ObservableCollection<OrderViewModel> (antecedent.Result); } }, TaskScheduler. FromCurrentSynchronizationContext ()); } } Right-click on the Views folder and choose Add | New File…, select the Forms | Forms Content Page Xaml, name it OrderListView, and click on New: <?xml version="1.0" encoding="UTF-8"?> <ContentPage x_Class="XamFormsMVVM.OrderListView" Title="Orders"> <ContentPage.Content> <ListView ItemsSource="{Binding Orders}"/> </ContentPage.Content> </ContentPage> Go to XmaFormsMVVM.cs and replace the contents with the following code: public App() { if (!Resolver.IsSet) { SetIoc (); } RegisterViews(); MainPage = new NavigationPage((Page)ViewFactory. CreatePage<OrderListViewModel, OrderListView>()); } private void SetIoc() { var resolverContainer = new SimpleContainer(); Resolver.SetResolver (resolverContainer.GetResolver()); } private void RegisterViews() { ViewFactory.Register<OrderListView, OrderListViewModel>(); } Run the application, and you will get results like the following screenshots: For Android: For iOS: How it works… A cross-platform solution should share as much logic and common operations as possible, such as retrieving and/or updating data in a local database or over the network, having your logic centralized, and coordinating components. With Xamarin.Forms, you even have a cross-platform UI, but this shouldn't stop you from separating the concerns correctly; the more abstracted you are from the user interface and programming against interfaces, the easier it is to adapt to changes and remove or add components. Starting with models and creating a DataService implementation class with its equivalent interface, IDataService retrieves raw JSON data over the network from the Parse API and converts it to a list of OrderModel, which are POCO classes with just one property. Every time you invoke the GetOrdersAsync method, you get the same 100 orders from the server. Notice how we used the Dependency attribute declaration above the namespace to instruct DependencyService that we want to register this implementation class for the interface. We took a step to improve the performance of the REST client API; although we do use the HTTPClient package, we pass a delegate handler, NativeMessageHandler, when constructing in the GetClient() method. This handler is part of the modernhttpclient NuGet package and it manages undercover to use a native REST API for each platform: NSURLSession in iOS and OkHttp in Android. The IDataService interface is used by the DataRepository implementation, which acts as a simple intermediate repository layer converting the POCO OrderModel received from the server in OrderViewModel instances. Any model that is meant to be used on a view is a ViewModel, the view's model, and also, when retrieving and updating data, you don't carry business logic. Only data logic that is known should be included as data transfer objects. Dependencies, such as in our case, where we have a dependency of IDataService for the DataRepository to work, should be clear to classes that will use the component, which is why we create a default empty constructor required from the XLabs ViewFactory class, but in reality, we always invoke the constructor that accepts an IDataService instance; this way, when we unit test this unit, we can pass our mock IDataService class and test the functionality of the methods. We are using the DependencyService class to register the implementation to its equivalent IDataRepository interface here as well. OrderViewModel inherits XLabs.Forms.ViewModel; it is a simple ViewModel class with one property raising property change notifications and accepting an OrderModel instance as a dependency in the default constructor. We override the ToString() method too for a default string representation of the object, which simplifies the ListView control without requiring us, in our example, to use a custom cell with DataTemplate. The second ViewModel in our architecture is the OrderListViewModel, which inherits XLabs.Forms.ViewModel too and has a dependency of IDataRepository, following the same pattern with a default constructor and a constructor with the dependency argument. This ViewModel is responsible for retrieving a list of OrderViewModel and holding it to an ObservableCollection<OrderViewModel> instance that raises collection change notifications. In the constructor, we invoke the GetOrdersAsync() method and register an action delegate handler to be invoked on the main thread when the task has finished passing the orders received in a new ObservableCollection<OrderViewModel> instance set to the Orders property. The view of this recipe is super simple: in XAML, we set the title property which is used in the navigation bar for each platform and we leverage the built-in data-binding mechanism of Xamarin.Forms to bind the Orders property in the ListView ItemsSource property. This is how we abstract the ViewModel from the view. But we need to provide a BindingContext class to the view while still not coupling the ViewModel to the view, and Xamarin Forms Labs is a great framework for filling the gap. XLabs has a ViewFactory class; with this API, we can register the mapping between a view and a ViewModel, and the framework will take care of injecting our ViewModel into the BindingContext class of the view. When a page is required in our application, we use the ViewFactory.CreatePage class, which will construct and provide us with the desired instance. Xamarin Forms Labs uses a dependency resolver internally; this has to be set up early in the application startup entry point, so it is handled in the App.cs constructor. Run the iOS application in the simulator or device and in your preferred Android emulator or device; the result is the same with the equivalent native themes for each platform. Summary Xamarin.Forms is a great cross-platform UI framework that you can use to describe your user interface code declaratives in XAML, and it will be translated into the equivalent native views and pages with the ability of customizing each native application layer. Xamarin.Forms and MVVM are made for each other; the pattern fits naturally into the design of native cross-platform mobile applications and abstracts the view from the data easy using the built-in data-binding mechanism. Resources for Article: Further resources on this subject: Code Sharing Between iOS and Android [Article] Working with Xamarin.Android [Article] Sharing with MvvmCross [Article]
Read more
  • 0
  • 0
  • 16314

article-image-rendering-stereoscopic-3d-models-using-opengl
Packt
24 Aug 2015
8 min read
Save for later

Rendering Stereoscopic 3D Models using OpenGL

Packt
24 Aug 2015
8 min read
In this article, by Raymond C. H. Lo and William C. Y. Lo, authors of the book OpenGL Data Visualization Cookbook, we will demonstrate how to visualize data with stunning stereoscopic 3D technology using OpenGL. Stereoscopic 3D devices are becoming increasingly popular, and the latest generation's wearable computing devices (such as the 3D vision glasses from NVIDIA, Epson, and more recently, the augmented reality 3D glasses from Meta) can now support this feature natively. The ability to visualize data in a stereoscopic 3D environment provides a powerful and highly intuitive platform for the interactive display of data in many applications. For example, we may acquire data from the 3D scan of a model (such as in architecture, engineering, and dentistry or medicine) and would like to visualize or manipulate 3D objects in real time. Unfortunately, OpenGL does not provide any mechanism to load, save, or manipulate 3D models. Thus, to support this, we will integrate a new library named Open Asset Import Library (Assimp) into our code. The main dependencies include the GLFW library that requires OpenGL version 3.2 and higher. (For more resources related to this topic, see here.) Stereoscopic 3D rendering 3D television and 3D glasses are becoming much more prevalent with the latest trends in consumer electronics and technological advances in wearable computing. In the market, there are currently many hardware options that allow us to visualize information with stereoscopic 3D technology. One common format is side-by-side 3D, which is supported by many 3D glasses as each eye sees an image of the same scene from a different perspective. In OpenGL, creating side-by-side 3D rendering requires asymmetric adjustment as well as viewport adjustment (that is, the area to be rendered) – asymmetric frustum parallel projection or equivalently to lens-shift in photography. This technique introduces no vertical parallax and widely adopted in the stereoscopic rendering. To illustrate this concept, the following diagram shows the geometry of the scene that a user sees from the right eye: The intraocular distance (IOD) is the distance between two eyes. As we can see from the diagram, the Frustum Shift represents the amount of skew/shift for asymmetric frustrum adjustment. Similarly, for the left eye image, we perform the transformation with a mirrored setting. The implementation of this setup is described in the next section. How to do it... The following code illustrates the steps to construct the projection and view matrices for stereoscopic 3D visualization. The code uses the intraocular distance, the distance of the image plane, and the distance of the near clipping plane to compute the appropriate frustum shifts value. In the source file, common/controls.cpp, we add the implementation for the stereo 3D matrix setup: void computeStereoViewProjectionMatrices(GLFWwindow* window, float IOD, float depthZ, bool left_eye){ int width, height; glfwGetWindowSize(window, &width, &height); //up vector glm::vec3 up = glm::vec3(0,-1,0); glm::vec3 direction_z(0, 0, -1); //mirror the parameters with the right eye float left_right_direction = -1.0f; if(left_eye) left_right_direction = 1.0f; float aspect_ratio = (float)width/(float)height; float nearZ = 1.0f; float farZ = 100.0f; double frustumshift = (IOD/2)*nearZ/depthZ; float top = tan(g_initial_fov/2)*nearZ; float right = aspect_ratio*top+frustumshift*left_right_direction; //half screen float left = -aspect_ratio*top+frustumshift*left_right_direction; float bottom = -top; g_projection_matrix = glm::frustum(left, right, bottom, top, nearZ, farZ); // update the view matrix g_view_matrix = glm::lookAt( g_position-direction_z+ glm::vec3(left_right_direction*IOD/2, 0, 0), //eye position g_position+ glm::vec3(left_right_direction*IOD/2, 0, 0), //centre position up //up direction ); In the rendering loop in main.cpp, we define the viewports for each eye (left and right) and set up the projection and view matrices accordingly. For each eye, we translate our camera position by half of the intraocular distance, as illustrated in the previous figure: if(stereo){ //draw the LEFT eye, left half of the screen glViewport(0, 0, width/2, height); //computes the MVP matrix from the IOD and virtual image plane distance computeStereoViewProjectionMatrices(g_window, IOD, depthZ, true); //gets the View and Model Matrix and apply to the rendering glm::mat4 projection_matrix = getProjectionMatrix(); glm::mat4 view_matrix = getViewMatrix(); glm::mat4 model_matrix = glm::mat4(1.0); model_matrix = glm::translate(model_matrix, glm::vec3(0.0f, 0.0f, -depthZ)); model_matrix = glm::rotate(model_matrix, glm::pi<float>() * rotateY, glm::vec3(0.0f, 1.0f, 0.0f)); model_matrix = glm::rotate(model_matrix, glm::pi<float>() * rotateX, glm::vec3(1.0f, 0.0f, 0.0f)); glm::mat4 mvp = projection_matrix * view_matrix * model_matrix; //sends our transformation to the currently bound shader, //in the "MVP" uniform variable glUniformMatrix4fv(matrix_id, 1, GL_FALSE, &mvp[0][0]); //render scene, with different drawing modes if(drawTriangles) obj_loader->draw(GL_TRIANGLES); if(drawPoints) obj_loader->draw(GL_POINTS); if(drawLines) obj_loader->draw(GL_LINES); //Draw the RIGHT eye, right half of the screen glViewport(width/2, 0, width/2, height); computeStereoViewProjectionMatrices(g_window, IOD, depthZ, false); projection_matrix = getProjectionMatrix(); view_matrix = getViewMatrix(); model_matrix = glm::mat4(1.0); model_matrix = glm::translate(model_matrix, glm::vec3(0.0f, 0.0f, -depthZ)); model_matrix = glm::rotate(model_matrix, glm::pi<float>() * rotateY, glm::vec3(0.0f, 1.0f, 0.0f)); model_matrix = glm::rotate(model_matrix, glm::pi<float>() * rotateX, glm::vec3(1.0f, 0.0f, 0.0f)); mvp = projection_matrix * view_matrix * model_matrix; glUniformMatrix4fv(matrix_id, 1, GL_FALSE, &mvp[0][0]); if(drawTriangles) obj_loader->draw(GL_TRIANGLES); if(drawPoints) obj_loader->draw(GL_POINTS); if(drawLines) obj_loader->draw(GL_LINES); } The final rendering result consists of two separate images on each side of the display, and note that each image is compressed horizontally by a scaling factor of two. For some display systems, each side of the display is required to preserve the same aspect ratio depending on the specifications of the display. Here are the final screenshots of the same models in true 3D using stereoscopic 3D rendering: Here's the rendering of the architectural model in stereoscopic 3D: How it works... The stereoscopic 3D rendering technique is based on the parallel axis and asymmetric frustum perspective projection principle. In simpler terms, we rendered a separate image for each eye as if the object was seen at a different eye position but viewed on the same plane. Parameters such as the intraocular distance and frustum shift can be dynamically adjusted to provide the desired 3D stereo effects. For example, by increasing or decreasing the frustum asymmetry parameter, the object will appear to be moved in front or behind the plane of the screen. By default, the zero parallax plane is set to the middle of the view volume. That is, the object is set up so that the center position of the object is positioned at the screen level, and some parts of the object will appear in front of or behind the screen. By increasing the frustum asymmetry (that is, positive parallax), the scene will appear to be pushed behind the screen. Likewise, by decreasing the frustum asymmetry (that is, negative parallax), the scene will appear to be pulled in front of the screen. The glm::frustum function sets up the projection matrix, and we implemented the asymmetric frustum projection concept illustrated in the drawing. Then, we use the glm::lookAt function to adjust the eye position based on the IOP value we have selected. To project the images side by side, we use the glViewport function to constrain the area within which the graphics can be rendered. The function basically performs an affine transformation (that is, scale and translation) which maps the normalized device coordinate to the window coordinate. Note that the final result is a side-by-side image in which the graphic is scaled by a factor of two vertically (or compressed horizontally). Depending on the hardware configuration, we may need to adjust the aspect ratio. The current implementation supports side-by-side 3D, which is commonly used in most wearable Augmented Reality (AR) or Virtual Reality (VR) glasses. Fundamentally, the rendering technique, namely the asymmetric frustum perspective projection described in our article, is platform-independent. For example, we have successfully tested our implementation on the Meta 1 Developer Kit (https://www.getameta.com/products) and rendered the final results on the optical see-through stereoscopic 3D display: Here is the front view of the Meta 1 Developer Kit, showing the optical see-through stereoscopic 3D display and 3D range-sensing camera: The result is shown as follows, with the stereoscopic 3D graphics rendered onto the real world (which forms the basis of augmented reality): See also In addition, we can easily extend our code to support shutter glasses-based 3D monitors by utilizing the Quad Buffered OpenGL APIs (refer to the GL_BACK_RIGHT and GL_BACK_LEFT flags in the glDrawBuffer function). Unfortunately, such 3D formats require specific hardware synchronization and often require higher frame rate display (for example, 120Hz) as well as a professional graphics card. Further information on how to implement stereoscopic 3D in your application can be found at http://www.nvidia.com/content/GTC-2010/pdfs/2010_GTC2010.pdf. Summary In this article, we covered how to visualize data with stunning stereoscopic 3D technology using OpenGL. OpenGL does not provide any mechanism to load, save, or manipulate 3D models. Thus, to support this, we have integrated a new library named Assimp into the code. Resources for Article: Further resources on this subject: Organizing a Virtual Filesystem [article] Using OpenCL [article] Introduction to Modern OpenGL [article]
Read more
  • 0
  • 1
  • 16295
Modal Close icon
Modal Close icon